[vispy] 08/10: Imported Upstream version 0.3.0

Frédéric-Emmanuel Picca picca at moszumanska.debian.org
Sat Aug 30 17:17:32 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 6285b1f4a9d408c92d131eec63fa9b752321cbec
Author: Picca Frédéric-Emmanuel <picca at debian.org>
Date:   Sat Aug 30 09:47:04 2014 +0200

    Imported Upstream version 0.3.0
---
 .coveragerc                                        |   17 +
 .gitignore                                         |    6 +
 .travis.yml                                        |  181 +
 CONTRIBUTORS.txt                                   |   55 +
 Makefile                                           |   95 +
 README.md                                          |   44 -
 README.rst                                         |  137 +
 codegen/annotations.py                             |  660 ++
 codegen/createglapi.py                             |  811 +-
 codegen/headerparser.py                            |  367 +-
 codegen/headers/egl.h                              |  329 +
 codegen/headers/webgl.idl                          |  743 ++
 dev/disc_collection.py                             |  113 -
 dev/visual_experiments.py                          |   33 -
 doc/app.rst                                        |    2 +-
 doc/architecture.svg                               |  689 --
 doc/color.rst                                      |    6 +
 doc/conf.py                                        |   38 +-
 doc/event.rst                                      |   36 -
 doc/ext/docscrape.py                               |   93 +-
 doc/ext/docscrape_sphinx.py                        |   29 +-
 doc/ext/examplesgenerator.py                       |   81 +-
 doc/ext/glapigenerator.py                          |   49 +-
 doc/ext/gloooverviewgenerator.py                   |   31 +-
 doc/ext/numpydoc.py                                |   26 +-
 doc/ext/scriptnamemangler.py                       |    1 +
 doc/geometry.rst                                   |    6 +
 doc/gloo.rst                                       |   23 +-
 doc/index.rst                                      |   88 +-
 doc/io.rst                                         |    6 +
 doc/mpl_plot.rst                                   |    6 +
 doc/plot.rst                                       |    6 +
 doc/releasenotes.rst                               |   18 +-
 doc/scene.rst                                      |   45 +
 doc/shaders.svg                                    | 1318 ---
 doc/themes/scikit-image/layout.html                |   10 +-
 doc/themes/scikit-image/search.html                |   31 +-
 doc/themes/scikit-image/static/css/custom.css      |   21 +-
 doc/themes/scikit-image/static/js/docversions.js   |   21 -
 doc/util.rst                                       |   43 +-
 doc/vispy.rst                                      |    6 +
 doc/visuals.rst                                    |    8 -
 examples/app/app-glut.py                           |   68 -
 examples/app/app-pyglet.py                         |   63 -
 examples/app/app-qt.py                             |   62 -
 .../gloo/animate_images.py}                        |   75 +-
 examples/basics/gloo/animate_images_slice.py       |  137 +
 .../gloo/animate_shape.py}                         |   80 +-
 .../gloo/display_lines.py}                         |   74 +-
 .../gloo/display_points.py}                        |   50 +-
 .../gloo/display_shape.py}                         |   42 +-
 examples/basics/gloo/gpuimage.py                   |  111 +
 .../hello-fbo.py => basics/gloo/hello_fbo.py}      |   91 +-
 examples/basics/gloo/post_processing.py            |  159 +
 examples/basics/gloo/rotate_cube.py                |  170 +
 examples/basics/gloo/start.py                      |   18 +
 examples/basics/gloo/start_shaders.py              |   38 +
 examples/basics/plotting/mpl_plot.py               |   51 +
 examples/basics/plotting/vispy_plot.py             |   18 +
 examples/basics/scene/grid.py                      |   79 +
 examples/basics/scene/grid_large.py                |   40 +
 examples/basics/scene/image.py                     |   31 +
 examples/basics/scene/isocurve.py                  |   43 +
 examples/basics/scene/isosurface.py                |   50 +
 examples/basics/scene/line.py                      |   47 +
 examples/basics/scene/modular_shaders/editor.py    |  189 +
 examples/basics/scene/modular_shaders/sandbox.py   |  609 ++
 examples/basics/scene/nested_viewbox.py            |  146 +
 examples/basics/scene/surface_plot.py              |   37 +
 examples/basics/scene/text.py                      |   43 +
 examples/basics/scene/viewbox.py                   |   99 +
 examples/basics/visuals/custom_visual.py           |  190 +
 examples/basics/visuals/image_transforms.py        |  116 +
 examples/basics/visuals/image_visual.py            |   37 +
 examples/basics/visuals/line.py                    |   85 +
 examples/basics/visuals/line_plot.py               |   33 +
 examples/basics/visuals/line_transform.py          |   91 +
 examples/basics/visuals/line_update.py             |   46 +
 examples/basics/visuals/markers.py                 |   74 +
 examples/basics/visuals/mesh.py                    |   95 +
 examples/basics/visuals/modular_components.py      |  179 +
 examples/basics/visuals/modular_line.py            |   44 +
 examples/basics/visuals/modular_mesh.py            |  126 +
 examples/basics/visuals/modular_point.py           |   39 +
 examples/basics/visuals/polygon_visual.py          |   93 +
 examples/basics/visuals/reactive_ellipse.py        |   47 +
 examples/basics/visuals/reactive_polygon.py        |   58 +
 .../basics/visuals/reactive_regular_polygon.py     |   55 +
 examples/basics/visuals/text_visual.py             |   41 +
 examples/benchmark/simple-vispy.py                 |   33 -
 .../benchmark/{simple-glut.py => simple_glut.py}   |   25 +-
 examples/benchmark/simple_vispy.py                 |   24 +
 examples/demo/atom.py                              |  192 -
 examples/demo/game_of_life.py                      |  156 -
 examples/demo/gloo/atom.py                         |  171 +
 examples/demo/{ => gloo}/boids.py                  |  144 +-
 examples/demo/gloo/brain.py                        |  156 +
 examples/demo/gloo/camera.py                       |   72 +
 examples/demo/{ => gloo}/cloud.py                  |  124 +-
 examples/demo/gloo/donut.py                        |  190 +
 examples/demo/{ => gloo}/fireworks.py              |   83 +-
 examples/demo/{ => gloo}/galaxy.py                 |  135 +-
 examples/demo/gloo/game_of_life.py                 |  182 +
 .../gloo/glsl_sandbox_cube.py}                     |  117 +-
 examples/demo/gloo/graph.py                        |  186 +
 examples/demo/gloo/grayscott.py                    |  207 +
 examples/demo/gloo/imshow.py                       |  130 +
 examples/demo/gloo/imshow_cuts.py                  |  179 +
 examples/demo/gloo/jfa/fragment_display.glsl       |   23 +
 examples/demo/gloo/jfa/fragment_flood.glsl         |  159 +
 examples/demo/gloo/jfa/fragment_seed.glsl          |   21 +
 examples/demo/gloo/jfa/jfa_translation.py          |  254 +
 examples/demo/gloo/jfa/jfa_vispy.py                |  117 +
 examples/demo/gloo/jfa/vertex.glsl                 |   24 +
 examples/demo/gloo/jfa/vertex_vispy.glsl           |   26 +
 examples/demo/gloo/mandelbrot.py                   |  186 +
 examples/demo/{ => gloo}/markers.py                |   41 +-
 examples/demo/gloo/molecular_viewer.py             |  197 +
 examples/demo/gloo/ndscatter.py                    |  153 +
 examples/demo/gloo/offscreen.py                    |  143 +
 examples/demo/gloo/rain.py                         |  140 +
 examples/demo/gloo/raytracing.py                   |  255 +
 examples/demo/gloo/realtime_signals.py             |  164 +
 .../demo/{show-markers.py => gloo/show_markers.py} |   88 +-
 examples/demo/gloo/signals.py                      |  130 +
 examples/demo/gloo/spacy.py                        |  168 +
 examples/demo/gloo/terrain.py                      |  206 +
 examples/demo/gloo/unstructured_2d.py              |  218 +
 examples/demo/gloo/voronoi.py                      |  108 +
 examples/howto/client-buffers.py                   |  187 -
 examples/howto/rotate-cube.py                      |  173 -
 examples/howto/split-screen.py                     |  181 -
 examples/howto/start.py                            |   17 -
 examples/ipynb/display_points.ipynb                |  152 +
 examples/ipynb/display_shape.ipynb                 |  116 +
 examples/ipynb/donut.ipynb                         |  255 +
 examples/ipynb/fireworks.ipynb                     |  195 +
 examples/ipynb/galaxy.ipynb                        |  238 +
 examples/ipynb/mandelbrot.ipynb                    |  251 +
 examples/ipynb/post_processing.ipynb               |  228 +
 examples/ipynb/rain.ipynb                          |  202 +
 examples/ipynb/spacy.ipynb                         |  220 +
 examples/ipynb/voronoi.ipynb                       |  172 +
 examples/rawgl/rawgl-cube.py                       |  202 -
 examples/rawgl/rawgl-fireworks.py                  |  222 -
 examples/spinning-cube2.py                         |  121 -
 examples/texturing.py                              |  112 -
 .../app-event.py => tutorial/app/app_events.py}    |   48 +-
 examples/{app/simple.py => tutorial/app/fps.py}    |   32 +-
 examples/tutorial/app/shared_context.py            |   72 +
 examples/{ => tutorial}/app/simple.py              |   28 +-
 examples/tutorial/gl/cube.py                       |  234 +
 examples/tutorial/gl/fireworks.py                  |  162 +
 examples/tutorial/gl/quad.py                       |  118 +
 examples/tutorial/gloo/colored_cube.py             |   89 +
 examples/tutorial/gloo/colored_quad.py             |   54 +
 examples/tutorial/gloo/lighted_cube.py             |  153 +
 examples/tutorial/gloo/outlined_cube.py            |  107 +
 examples/tutorial/gloo/rotating_quad.py            |   68 +
 examples/tutorial/gloo/textured_cube.py            |  103 +
 examples/tutorial/gloo/textured_quad.py            |   62 +
 make/__init__.py                                   |    2 +-
 make/__main__.py                                   |    2 +-
 make/make.py                                       |  380 +-
 nosetests.py                                       |    6 -
 setup.py                                           |  105 +-
 _link_vispy.py => vispy.proxy.py                   |    7 +-
 vispy/__init__.py                                  |  108 +-
 vispy/app/__init__.py                              |   67 +-
 vispy/app/_config.py                               |   20 +
 vispy/app/_default_app.py                          |   71 +
 vispy/app/application.py                           |  203 +-
 vispy/app/backends/__init__.py                     |   40 +-
 vispy/app/backends/_egl.py                         |  256 +
 vispy/app/backends/_glfw.py                        |  478 ++
 vispy/app/backends/_glut.py                        |  502 ++
 vispy/app/backends/_ipynb_static.py                |  217 +
 vispy/app/backends/_ipynb_vnc.py                   |  373 +
 vispy/app/backends/_pyglet.py                      |  453 ++
 vispy/app/backends/_pyqt4.py                       |   32 +
 vispy/app/backends/_pyside.py                      |   33 +
 vispy/app/backends/_qt.py                          |  440 +
 vispy/app/backends/_sdl2.py                        |  458 ++
 vispy/app/backends/_template.py                    |  229 +
 vispy/app/backends/_test.py                        |    8 +
 vispy/app/backends/_wx.py                          |  425 +
 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/base.py                                  |  252 +
 vispy/app/canvas.py                                |  775 +-
 vispy/{shaders => app/tests}/__init__.py           |    0
 vispy/app/tests/qt-designer.ui                     |    2 +-
 vispy/app/tests/test_app.py                        |  417 +
 vispy/app/tests/test_backends.py                   |  256 +-
 vispy/app/tests/test_context.py                    |   72 +
 vispy/app/tests/test_qt.py                         |   69 +-
 vispy/app/tests/test_simultaneous.py               |  128 +
 vispy/app/timer.py                                 |  142 +-
 vispy/color/__init__.py                            |   13 +
 vispy/color/_color.py                              |  606 ++
 vispy/color/_color_dict.py                         |  182 +
 vispy/{shaders => color/tests}/__init__.py         |    0
 vispy/color/tests/test_color.py                    |  200 +
 vispy/data/crate.bz2                               |  Bin 55642 -> 0 bytes
 vispy/data/cube.obj                                |   36 -
 vispy/data/triceratops.obj                         | 8492 --------------------
 vispy/{shaders => ext}/__init__.py                 |    0
 vispy/ext/_mpl_py3k_compat.py                      |   22 +
 vispy/ext/cocoapy.py                               | 1507 ++++
 vispy/ext/egl.py                                   |  369 +
 vispy/ext/fontconfig.py                            |  119 +
 vispy/ext/freetype.py                              |  499 ++
 vispy/ext/gdi32plus.py                             |  189 +
 vispy/ext/glfw.py                                  |  641 ++
 vispy/ext/gzip_open.py                             |   19 +
 vispy/ext/mplexporter.py                           |  693 ++
 vispy/ext/mplutils.py                              |  353 +
 vispy/ext/ordereddict.py                           |    7 +
 vispy/ext/png.py                                   | 2215 +++++
 vispy/ext/py24_ordereddict.py                      |  267 +
 vispy/{util => ext}/six.py                         |  207 +-
 vispy/geometry/__init__.py                         |   19 +
 vispy/geometry/_triangulation_debugger.py          |  171 +
 vispy/geometry/calculations.py                     |   80 +
 vispy/geometry/generation.py                       |  198 +
 vispy/geometry/isocurve.py                         |  175 +
 vispy/geometry/isosurface.py                       |  471 ++
 vispy/geometry/meshdata.py                         |  461 ++
 vispy/geometry/polygon.py                          |  137 +
 vispy/geometry/rect.py                             |  157 +
 vispy/{shaders => geometry/tests}/__init__.py      |    0
 vispy/geometry/tests/test_generation.py            |   28 +
 vispy/geometry/tests/test_triangulation.py         |  508 ++
 vispy/geometry/triangulation.py                    |  965 +++
 vispy/gloo/__init__.py                             |   74 +-
 vispy/gloo/buffer.py                               | 1378 ++--
 vispy/gloo/framebuffer.py                          |  612 +-
 vispy/gloo/gl/__init__.py                          |  220 +-
 vispy/gloo/gl/_angle.py                            |  986 +++
 vispy/gloo/gl/_constants.py                        |  623 +-
 vispy/gloo/gl/_constants_ext.py                    |   96 -
 vispy/gloo/gl/_desktop.py                          | 1511 +++-
 vispy/gloo/gl/_desktop_ext.py                      |   30 -
 vispy/gloo/gl/_proxy.py                            |  503 ++
 vispy/gloo/gl/_pyopengl.py                         |  335 +
 vispy/gloo/gl/angle.py                             |   42 +
 vispy/gloo/gl/desktop.py                           |  204 +-
 vispy/gloo/gl/ext.py                               |    9 -
 vispy/gloo/gl/pyopengl.py                          |  142 +
 vispy/{shaders => gloo/gl/tests}/__init__.py       |    0
 vispy/gloo/gl/tests/test_basics.py                 |  276 +
 vispy/gloo/gl/tests/test_functionality.py          |  557 ++
 vispy/gloo/gl/tests/test_names.py                  |  232 +
 vispy/gloo/gl/tests/test_use.py                    |   58 +
 vispy/gloo/gl/webgl.py                             |   29 +
 vispy/gloo/globject.py                             |  190 +-
 vispy/gloo/initialize.py                           |   18 +
 vispy/gloo/program.py                              |  975 +--
 vispy/gloo/shader.py                               |  385 +-
 vispy/{shaders => gloo/tests}/__init__.py          |    0
 vispy/gloo/tests/test_buffer.py                    |  810 +-
 vispy/gloo/tests/test_framebuffer.py               |  206 -
 vispy/gloo/tests/test_globject.py                  |  222 +-
 vispy/gloo/tests/test_program.py                   |  109 +-
 vispy/gloo/tests/test_shader.py                    |   73 +-
 vispy/gloo/tests/test_texture.py                   |  817 +-
 vispy/gloo/tests/test_use_gloo.py                  |  110 +
 vispy/gloo/tests/test_variable.py                  |  142 +-
 vispy/gloo/tests/test_wrappers.py                  |  106 +
 vispy/gloo/texture.py                              | 1504 ++--
 vispy/gloo/util.py                                 |  109 +
 vispy/gloo/variable.py                             |  682 +-
 vispy/gloo/wrappers.py                             |  674 ++
 vispy/html/static/js/vispy.js                      |  190 +
 vispy/io/__init__.py                               |   19 +
 vispy/io/_data/spatial-filters.npy                 |  Bin 0 -> 65616 bytes
 vispy/io/datasets.py                               |   34 +
 vispy/io/image.py                                  |  237 +
 vispy/io/mesh.py                                   |   76 +
 vispy/{shaders => io/tests}/__init__.py            |    0
 vispy/io/tests/test_image.py                       |   45 +
 vispy/io/tests/test_io.py                          |   71 +
 vispy/{util/dataio => io}/wavefront.py             |  234 +-
 vispy/mpl_plot/__init__.py                         |   21 +
 vispy/mpl_plot/_mpl_to_vispy.py                    |  199 +
 vispy/{shaders => mpl_plot/tests}/__init__.py      |    0
 vispy/mpl_plot/tests/test_show_vispy.py            |   30 +
 vispy/plot/__init__.py                             |   11 +
 vispy/plot/plot.py                                 |   37 +
 vispy/scene/__init__.py                            |   57 +
 vispy/scene/cameras.py                             |  595 ++
 vispy/scene/canvas.py                              |  338 +
 vispy/scene/components/__init__.py                 |   11 +
 vispy/scene/components/color.py                    |   93 +
 vispy/scene/components/component.py                |  136 +
 vispy/scene/components/material.py                 |  103 +
 vispy/scene/components/normal.py                   |   90 +
 vispy/scene/components/texture.py                  |  151 +
 vispy/scene/components/vertex.py                   |  223 +
 vispy/scene/entity.py                              |  331 +
 vispy/scene/events.py                              |  378 +
 vispy/scene/shaders/__init__.py                    |   15 +
 vispy/scene/shaders/compiler.py                    |  217 +
 vispy/scene/shaders/function.py                    | 1134 +++
 vispy/scene/shaders/parsing.py                     |  140 +
 vispy/scene/shaders/program.py                     |   83 +
 vispy/{shaders => scene/shaders/tests}/__init__.py |    0
 vispy/scene/shaders/tests/test_function.py         |  450 ++
 vispy/scene/shaders/tests/test_parsing.py          |   49 +
 vispy/scene/subscene.py                            |   49 +
 vispy/scene/systems.py                             |  106 +
 vispy/{shaders => scene/tests}/__init__.py         |    0
 vispy/scene/transforms/__init__.py                 |   32 +
 vispy/scene/transforms/_util.py                    |  144 +
 vispy/scene/transforms/base_transform.py           |  183 +
 vispy/scene/transforms/chain.py                    |  234 +
 vispy/scene/transforms/linear.py                   |  401 +
 vispy/scene/transforms/nonlinear.py                |  162 +
 vispy/scene/transforms/tests/test_transforms.py    |  233 +
 vispy/scene/visuals/__init__.py                    |   35 +
 vispy/scene/visuals/ellipse.py                     |  129 +
 vispy/scene/visuals/gridlines.py                   |  105 +
 vispy/scene/visuals/image.py                       |  158 +
 vispy/scene/visuals/isocurve.py                    |   84 +
 vispy/scene/visuals/isosurface.py                  |   67 +
 vispy/scene/visuals/line/__init__.py               |    5 +
 vispy/scene/visuals/line/dash_atlas.py             |   85 +
 vispy/scene/visuals/line/fragment.py               |  321 +
 vispy/scene/visuals/line/line.py                   |  424 +
 vispy/scene/visuals/line/vertex.py                 |  251 +
 vispy/scene/visuals/line_plot.py                   |   96 +
 vispy/scene/visuals/markers.py                     |  334 +
 vispy/scene/visuals/mesh.py                        |  224 +
 vispy/scene/visuals/modular_line.py                |   28 +
 vispy/scene/visuals/modular_mesh.py                |   21 +
 vispy/scene/visuals/modular_point.py               |   46 +
 vispy/scene/visuals/modular_visual.py              |  353 +
 vispy/scene/visuals/polygon.py                     |  126 +
 vispy/scene/visuals/rectangle.py                   |  172 +
 vispy/scene/visuals/regular_polygon.py             |   69 +
 vispy/scene/visuals/surface_plot.py                |  148 +
 vispy/scene/visuals/tests/test_ellipse.py          |  125 +
 vispy/scene/visuals/tests/test_polygon.py          |  105 +
 vispy/scene/visuals/tests/test_regular_polygon.py  |  108 +
 vispy/scene/visuals/tests/test_sdf.py              |   39 +
 vispy/scene/visuals/tests/test_text.py             |   19 +
 vispy/scene/visuals/text/__init__.py               |    7 +
 vispy/scene/visuals/text/_sdf.py                   |  308 +
 vispy/scene/visuals/text/text.py                   |  475 ++
 vispy/scene/visuals/visual.py                      |   81 +
 vispy/scene/visuals/xyz_axis.py                    |   26 +
 vispy/scene/widgets/__init__.py                    |   13 +
 vispy/scene/widgets/anchor.py                      |   25 +
 vispy/scene/widgets/grid.py                        |   82 +
 vispy/scene/widgets/viewbox.py                     |  316 +
 vispy/scene/widgets/widget.py                      |  214 +
 vispy/shaders/shaders.py                           |  393 -
 vispy/shaders/transforms.py                        |  334 -
 vispy/testing/__init__.py                          |    8 +
 vispy/testing/_coverage.py                         |   36 +
 vispy/testing/_runners.py                          |  210 +
 vispy/testing/_testing.py                          |  375 +
 vispy/{shaders => testing/tests}/__init__.py       |    0
 vispy/testing/tests/test_testing.py                |   12 +
 vispy/util/__init__.py                             |   11 +-
 vispy/util/color.py                                |    5 -
 vispy/util/config.py                               |  376 +
 vispy/util/dataio/__init__.py                      |  132 -
 vispy/util/eq.py                                   |   41 +
 vispy/util/event.py                                |  687 +-
 vispy/util/fetching.py                             |  299 +
 vispy/util/filter.py                               |   44 +
 vispy/util/fonts/__init__.py                       |   14 +
 vispy/util/fonts/_freetype.py                      |   70 +
 vispy/util/fonts/_quartz.py                        |  192 +
 vispy/util/fonts/_triage.py                        |   36 +
 vispy/util/fonts/_vispy_fonts.py                   |   20 +
 vispy/util/fonts/_win32.py                         |  103 +
 vispy/{shaders => util/fonts/tests}/__init__.py    |    0
 vispy/util/fonts/tests/test_font.py                |   35 +
 vispy/util/keys.py                                 |   51 +-
 vispy/util/logs.py                                 |  328 +
 vispy/util/ordereddict.py                          |  127 -
 vispy/util/ptime.py                                |   26 +-
 vispy/util/test/test_vispy.py                      |    6 -
 vispy/{shaders => util/tests}/__init__.py          |    0
 vispy/util/tests/test_config.py                    |   51 +
 vispy/util/{test => tests}/test_emitter_group.py   |  114 +-
 vispy/util/{test => tests}/test_event_emitter.py   |  427 +-
 vispy/util/tests/test_import.py                    |  135 +
 vispy/util/tests/test_key.py                       |   15 +
 vispy/util/tests/test_logging.py                   |   39 +
 vispy/util/tests/test_run.py                       |   12 +
 vispy/util/tests/test_transforms.py                |   35 +
 vispy/util/tests/test_vispy.py                     |   43 +
 vispy/util/transforms.py                           |  386 +-
 vispy/util/wrappers.py                             |  140 +
 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
 416 files changed, 58975 insertions(+), 23403 deletions(-)

diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..9391327
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,17 @@
+[run]
+branch = True
+source = vispy
+#include = */vispy/*
+# Omit fonts/_quartz.py and _win32 for platform specificity
+omit =
+    */setup.py
+    */vispy/datasets/*
+    */vispy/app/backends/_egl.py
+    */vispy/gloo/gl/angle.py
+    */vispy/gloo/gl/_angle.py
+    */vispy/testing/*
+    */vispy/ext/*
+    */vispy/util/geometry/_triangulation_debugger.py
+    */examples/*
+    */vispy/util/fonts/_quartz.py
+    */vispy/util/fonts/_win32.py
diff --git a/.gitignore b/.gitignore
index 39789f1..f8b69c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
+*.c
+*.so
 *.pyc
 *.pyo
+*.DS_Store
 doc/_build
 /_website
 /_gh-pages
@@ -7,3 +10,6 @@ doc/_build
 build
 dist
 MANIFEST
+.coverage
+htmlcov
+vispy.egg-info
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..bf2beb3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,181 @@
+language: python
+
+# Here we use anaconda for 2.6 and 3.4, since it provides the simplest
+# interface for running different versions of Python. We could also use
+# it for 2.7, but the Ubuntu system has installable 2.7 Qt4-GL, which
+# allows for more complete testing.
+
+
+virtualenv:
+    system_site_packages: true
+
+
+env:
+    # Enable python 2 and python 3 builds
+    # Note that the 2.6 build doesn't get flake8, and runs old versions of
+    # Pyglet and GLFW to make sure we deal with those correctly
+    - PYTHON=2.6 DEPS=full TEST=standard  # functionality
+    - PYTHON=2.7 DEPS=full TEST=standard
+    - PYTHON=2.7 DEPS=minimal TEST=standard
+    - PYTHON=3.4 DEPS=full TEST=standard
+    - PYTHON=3.4 DEPS=minimal TEST=extra  # test file sizes, style, line endings
+
+
+before_install:
+    - REDIRECT_TO=/dev/stdout  # change to /dev/null to silence Travis
+    - travis_retry sudo apt-get -qq update;
+    - if [ "${PYTHON}" != "2.7" ]; then
+        wget -q http://repo.continuum.io/miniconda/Miniconda-2.2.2-Linux-x86_64.sh -O miniconda.sh;
+        chmod +x miniconda.sh;
+        ./miniconda.sh -b &> ${REDIRECT_TO};
+        export PATH=/home/$USER/anaconda/bin:$PATH;
+        conda update --yes --quiet conda &> ${REDIRECT_TO};
+        travis_retry sudo apt-get -qq -y install libgl1-mesa-dri;
+      fi;
+
+    - SRC_DIR=$(pwd)
+    # file size checks
+    - if [ "${TEST}" == "extra" ]; then
+        if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
+          GIT_TARGET_EXTRA="+refs/heads/${TRAVIS_BRANCH}";
+          GIT_SOURCE_EXTRA="+refs/pull/${TRAVIS_PULL_REQUEST}/merge";
+        else
+          GIT_TARGET_EXTRA="";
+          GIT_SOURCE_EXTRA="";
+        fi;
+        cd ~;
+        mkdir target-size-clone && cd target-size-clone;
+        git init &> ${REDIRECT_TO} && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git &>${REDIRECT_TO};
+        git fetch origin ${GIT_TARGET_EXTRA} &> ${REDIRECT_TO} && git checkout -qf FETCH_HEAD &> ${REDIRECT_TO} && cd ..;
+        TARGET_SIZE=`du -s target-size-clone | sed -e "s/\t.*//"`;
+        mkdir source-size-clone && cd source-size-clone;
+        git init &> ${REDIRECT_TO} && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git &> ${REDIRECT_TO};
+        git fetch origin ${GIT_SOURCE_EXTRA} &> ${REDIRECT_TO} && git checkout -qf FETCH_HEAD &> ${REDIRECT_TO} && cd ..;
+        SOURCE_SIZE=`du -s source-size-clone | sed -e "s/\t.*//"`;
+        if [ "${SOURCE_SIZE}" != "${TARGET_SIZE}" ]; then
+          SIZE_DIFF=`expr ${SOURCE_SIZE} - ${TARGET_SIZE}`;
+        else
+          SIZE_DIFF=0;
+        fi;
+      fi;
+
+
+install:
+    # Install numpy, nose, flake
+    - if [ "${PYTHON}" != "2.7" ]; then
+        conda create -n testenv --yes --quiet pip python=$PYTHON > ${REDIRECT_TO};
+        source activate testenv > ${REDIRECT_TO};
+        conda install --yes --quiet numpy nose > ${REDIRECT_TO};
+      else
+        travis_retry sudo apt-get -qq -y install python-numpy python-nose python-setuptools > ${REDIRECT_TO};
+      fi;
+    - pip install -q coveralls nose-timer
+    # Dont install flake8 on 2.6 to make sure tests still run without it
+    - if [ "${PYTHON}" != "2.6" ]; then
+        pip install -q flake8;
+      else
+        pip install -q unittest2;
+      fi
+    # helpful for debugging faults
+    - if [ "${PYTHON}" != 3.4 ]; then
+        pip install -q nose-faulthandler;
+      fi
+
+    # Install PyOpenGL
+    - if [ "${DEPS}" == "full" ]; then
+        travis_retry sudo apt-get -qq -y install freeglut3 > ${REDIRECT_TO};
+        if [ "${PYTHON}" == "2.7" ]; then
+          echo "Using OpenGL stable version (repos)";
+          travis_retry sudo apt-get -qq -y install python-opengl > ${REDIRECT_TO};
+        else
+          echo "Using OpenGL stable version (pip)";
+          pip install -q PyOpenGL;
+        fi;
+      fi;
+
+    # Use Pyglet alpha to get Py3.4 support
+    # Also install PyQt4, imaging (PIL or pillow), scipy, mpl, egl
+    # Test old (insufficient) version of Pyglet on 2.6
+    # On conda, can't install pyside-pyzo b/c it conflicts with pyqt4,
+    # which is required by matplotlib :(
+    # No wx on py3k, wxpython doesn't work there (!)
+    - if [ "${DEPS}" == "full" ]; then
+        if [ "${PYTHON}" == "2.6" ]; then
+          pip install -q pyglet;
+          conda install --yes --quiet wxpython > ${REDIRECT_TO};
+        else
+          pip install -q http://pyglet.googlecode.com/archive/tip.zip;
+        fi;
+        if [ "${PYTHON}" == "3.4" ]; then
+          conda install --yes --quiet pillow scipy matplotlib > ${REDIRECT_TO};
+        fi;
+        if [ "${PYTHON}" == "2.7" ]; then
+          travis_retry sudo apt-get -qq -y install python-qt4-gl python-imaging python-scipy python-matplotlib python-wxgtk2.8 > ${REDIRECT_TO};
+        fi;
+        travis_retry sudo apt-get -qq -y install libegl1-mesa;
+      fi;
+
+    # Install vispy
+    - cd ${SRC_DIR}
+    - python setup.py install > ${REDIRECT_TO}
+    - cd ~
+
+    # GLFW: version 2 shouldn't work (so let's try on Py2.6), version 3 will
+    - if [ "${PYTHON}" == "2.6" ] && [ "${DEPS}" == "full" ]; then
+        travis_retry sudo apt-get -qq install libglfw2 > ${REDIRECT_TO};
+      fi
+    - if [ "${PYTHON}" != "2.6" ] && [ "${DEPS}" == "full" ]; then
+        travis_retry sudo apt-get -qq install xorg-dev libglu1-mesa-dev > ${REDIRECT_TO};
+        git clone git://github.com/glfw/glfw.git &> ${REDIRECT_TO};
+        cd glfw;
+        cmake -DBUILD_SHARED_LIBS=true -DGLFW_BUILD_EXAMPLES=false -DGLFW_BUILD_TESTS=false -DGLFW_BUILD_DOCS=false . > ${REDIRECT_TO};
+        sudo make install > ${REDIRECT_TO};
+      fi
+
+    # Install SDL2 (on 2.6, let's only install sdl2-python to test handling)
+    - if [ "${DEPS}" == "full" ]; then
+        if [ "${PYTHON}" != "2.6" ]; then
+          cd ~;
+          travis_retry sudo apt-get -qq -y install mercurial libdbus-1-dev libgl1-mesa-dev libglu1-mesa-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev libasound2-dev libts-dev libudev-dev > ${REDIRECT_TO};
+          wget -q http://www.libsdl.org/release/SDL2-2.0.3.tar.gz;
+          tar xzf SDL2-2.0.3.tar.gz &> ${REDIRECT_TO};
+          cd SDL2-2.0.3;
+          mkdir build;
+          cd build;
+          ../configure --disable-audio --disable-render --disable-joystick --disable-haptic --disable-power --disable-cpuinfo &> ${REDIRECT_TO};
+          make -j 2 &> ${REDIRECT_TO};
+          sudo make install &> ${REDIRECT_TO};
+        fi;
+        pip install -q PySDL2;
+      fi;
+
+    # after manual builds we need to update .so search
+    - sudo ldconfig
+
+
+before_script:
+    # We need to create a (fake) display on Travis, let's use a funny resolution
+    - export DISPLAY=:99.0
+    - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render
+
+
+script:
+    - cd ${SRC_DIR}
+    - if [ "${TEST}" != "extra" ]; then
+        make nose_coverage;
+      fi;
+    # Require strict adherence to PEP8 and pyflakes (can use "# noqa" to skip)
+    - if [ "${TEST}" == "extra" ]; then
+        make extra;
+      fi;
+    - if [ "${TEST}" == "extra" ]; then
+        echo "Size difference ${SIZE_DIFF} kB";
+        test ${SIZE_DIFF} -lt 100;
+      fi;
+
+
+after_success:
+    # Need to run from source dir to execute appropriate "git" commands
+    - if [ "${TEST}" != "extra" ]; then
+        coveralls;
+      fi;
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
new file mode 100644
index 0000000..4239c85
--- /dev/null
+++ b/CONTRIBUTORS.txt
@@ -0,0 +1,55 @@
+Core team
+---------
+
+- Luke Campagnola
+
+- Almar Klein
+
+- Nicolas Rougier 
+
+- Cyrille Rossant
+
+- Eric Larson
+
+
+Contributors
+------------
+
+- Jerome Kieffer
+  keep it compatible with python 2.6 #68 
+
+- M S Suraj
+  Reactive visuals (#353, #368)
+  Visuals and geometry (#275)
+  Terrain generation using scipy delaunay triangulation (#191
+  Canvas object provides fps (#109)
+
+- Mustafa Furkan Kaptan
+  Fixes in read_pixels (#301)
+  Static IPython notebook backend (#333)
+  VNC IPython notebook backend (#356)
+
+- Matthieu Dartiailh
+  Add support for parent keyword at Canvas init (#294)
+  Implement the sizeHint method of CanvasBacjkend in Qt (#289)
+
+- Irwin Zaid
+  Support for 3D textures (#292)
+
+- gouarin
+  fix bug in game-of-life.py (#266) 
+
+- Per Rosengren
+  unstructured_2d demo (203, #276)
+- Matthias Vogelgesang
+  Simplify setup py (#70)
+
+- John David Reaver
+  Mandelbrot set demo (#239)
+  Several fixes (#231, #266)
+
+- joe311
+  Tailed arrow marker (#60)
+
+- Tom Pohl
+  added required constants and input keys to handle 3d textures (#27)
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..12fbd7a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,95 @@
+# simple makefile to simplify repetetive build env management tasks under posix
+
+CTAGS ?= ctags
+
+all: clean inplace test
+
+clean-pyc:
+	@find . -name "*.pyc" | xargs rm -f
+
+clean-so:
+	@find . -name "*.so" | xargs rm -f
+	@find . -name "*.pyd" | xargs rm -f
+
+clean-build:
+	@rm -rf build
+
+clean-ctags:
+	@rm -f tags
+
+clean-cache:
+	@find . -name "__pycache__" | xargs rm -rf
+
+clean: clean-build clean-pyc clean-so clean-ctags clean-cache
+	@echo "Cleaning build, pyc, so, ctags, and cache"
+
+clean-test: clean-build clean-pyc clean-ctags clean-cache
+	@echo "Cleaning build, pyc, ctags, and cache"
+
+in: inplace # just a shortcut
+inplace:
+	python setup.py build_ext -i
+
+nosetests: nose # alias
+
+# Test conditions, don't "clean-so" or builds won't work!
+
+nose: clean-test
+	python make test nose
+
+nose_coverage: clean-test
+	python make test nose 1
+
+coverage_html:
+	python make coverage_html
+
+gallery:
+	python make images gallery
+
+test: clean-test
+	python make test full
+
+test3: clean-test
+	python3 make test full
+
+flake: clean-test
+	python make test flake
+
+flake3: clean-test
+	python3 make test flake
+
+lineendings: clean-test
+	python make test lineendings
+
+extra: clean-test
+	python make test extra
+
+nobackend : clean-test
+	python make test nobackend
+
+pyqt4: clean-test
+	python make test pyqt4
+
+pyside: clean-test
+	python make test pyside
+
+pyglet: clean-test
+	python make test pyglet
+
+glfw: clean-test
+	python make test glfw
+
+sdl2: clean-test
+	python make test sdl2
+
+wx: clean-test
+	python make test wx
+
+egl: clean-test
+	python make test egl
+
+glut: clean-test
+	python make test glut
+
+ipynb_vnc: clean-test
+	python make test ipynb_vnc
diff --git a/README.md b/README.md
deleted file mode 100644
index 84192f2..0000000
--- a/README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-## 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/README.rst b/README.rst
new file mode 100644
index 0000000..6fe9ba6
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,137 @@
+Vispy: interactive scientific visualization in Python
+-----------------------------------------------------
+
+Main website: http://vispy.org
+
+|Build Status| |Coverage Status|
+
+----
+
+Vispy is a **high-performance interactive 2D/3D data visualization
+library**. Vispy leverages the computational power of modern **Graphics
+Processing Units (GPUs)** through the **OpenGL** library to display very
+large datasets. Applications of Vispy include:
+
+-  High-quality interactive scientific plots with millions of points.
+-  Direct visualization of real-time data.
+-  Fast interactive visualization of 3D models (meshes, volume
+   rendering).
+-  OpenGL visualization demos.
+-  Scientific GUIs with fast, scalable visualization widgets (Qt or
+   `IPython notebook <http://ipython.org/notebook.html>`__ with WebGL).
+
+
+Announcements
+-------------
+
+- **Release!** Version 0.3, August 29, 2014
+- **EuroSciPy 2014**: talk at Saturday 30, and sprint at Sunday 31, August 2014
+- `Article in Linux Magazine, French Edition <https://github.com/vispy/linuxmag-article>`__, July 2014
+- **GSoC 2014**: `two GSoC students are currently working on Vispy under the PSF umbrella <https://github.com/vispy/vispy/wiki/Project.%20GSoC-2014>`__
+- **Release!**, Version 0.2.1 04-11-2013
+- **Presentation at BI forum**, Budapest, 6 November 2013
+- **Presentation at Euroscipy**, Belgium, August 2013
+- **EuroSciPy Sprint**, Belgium, August 2013
+- **Release!** Version 0.1.0 14-08-2013
+
+
+Using Vispy
+-----------
+
+Vispy is a young library under heavy development at this time. It
+targets two categories of users:
+
+1. **Users knowing OpenGL**, or willing to learn OpenGL, who want to
+   create beautiful and fast interactive 2D/3D visualizations in Python
+   as easily as possible.
+2. **Scientists without any knowledge of OpenGL**, who are seeking a
+   high-level, high-performance plotting toolkit.
+
+If you're in the first category, you can already start using Vispy.
+Vispy offers a Pythonic, NumPy-aware, user-friendly interface for OpenGL
+ES 2.0 called **gloo**. You can focus on writing your GLSL code instead
+of dealing with the complicated OpenGL API - Vispy takes care of that
+automatically for you.
+
+If you're in the second category, we're starting to build experimental
+high-level plotting interfaces. Notably, Vispy now ships a very basic
+and experimental OpenGL backend for matplotlib.
+
+
+Installation
+------------
+
+Vispy runs on Python 2.6+ and Python 3.3+ and depends on NumPy. You also
+need a backend (PyQt4/PySide, glfw, GLUT, pyglet, or SDL).
+
+As Vispy is under heavy development at this time, we highly recommend
+you to use the development version on Github (master branch). You need
+to clone the repository and install Vispy with
+``python setup.py install``.
+
+
+Structure of Vispy
+------------------
+
+Currently, the main subpackages are:
+
+-  **app**: integrates an event system and offers a unified interface on
+   top of many window backends (Qt4, wx, glfw, GLUT, IPython notebook
+   with/without WebGL, and others). Relatively stable API.
+-  **gloo**: a Pythonic, object-oriented interface to OpenGL. Relatively
+   stable API.
+-  **mpl\_plot**: an OpenGL backend for matplotlib. Experimental.
+-  **scene**: this is the system underlying our upcoming high level
+   visualization interfaces. Under heavy development and still
+   experimental, it contains several modules.
+
+   -  **Visuals** are graphical abstractions representing 2D shapes, 3D
+      meshes, text, etc.
+   -  **Transforms** implement 2D/3D transformations implemented on both
+      CPU and GPU.
+   -  **Shaders** implements a shader composition system for plumbing
+      together snippets of GLSL code.
+   -  The **scene graph** tracks all objects within a transformation
+      graph.
+
+The API of all public interfaces are subject to change in the future,
+although **app** and **gloo** are *relatively* stable at this point.
+
+
+About us
+--------
+
+The core development team consists of:
+
+-  `Luke Campagnola <http://luke.campagnola.me/>`__
+-  `Almar Klein <http://www.almarklein.org/>`__
+-  `Eric Larson <http://larsoner.com>`__
+-  `Cyrille Rossant <http://cyrille.rossant.net>`__
+-  `Nicolas Rougier <http://www.loria.fr/~rougier/index.html>`__
+
+Four of us have written our own Python visualization toolkit
+(`PyQtGraph <http://www.pyqtgraph.org/>`__ by LC,
+`Visvis <https://code.google.com/p/visvis/>`__ by AK,
+`Glumpy <https://github.com/rougier/Glumpy>`__ by NR, and
+`Galry <https://github.com/rossant/galry>`__ by CR), and we decided to
+team up to create a unique high-performance, high-quality interactive
+visualization library.
+
+----
+
+External links
+--------------
+
+-  `User mailing
+   list <https://groups.google.com/forum/#!forum/vispy>`__
+-  `Dev mailing
+   list <https://groups.google.com/forum/#!forum/vispy-dev>`__
+-  `Dev chat room <https://gitter.im/vispy/vispy>`__
+-  `Wiki <http://github.com/vispy/vispy/wiki>`__
+-  `Gallery <http://vispy.org/gallery.html>`__
+-  `Documentation <http://vispy.readthedocs.org>`__
+
+.. |Build Status| image:: https://travis-ci.org/vispy/vispy.png?branch=master
+   :target: https://travis-ci.org/vispy/vispy
+.. |Coverage Status| image:: https://coveralls.io/repos/vispy/vispy/badge.png?branch=master
+   :target: https://coveralls.io/r/vispy/vispy?branch=master
diff --git a/codegen/annotations.py b/codegen/annotations.py
new file mode 100644
index 0000000..1a31617
--- /dev/null
+++ b/codegen/annotations.py
@@ -0,0 +1,660 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2013, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" 
+This module contains manual annotations for the gl backends. Together
+with the header files, we can generatre the full ES 2.0 API.
+
+Every function-annotations consists of sections that apply to one or
+more backends. If no backends are specified in the first section, it
+applies to all backends.
+"""
+
+import ctypes
+
+
+## bind / gen / delete stuff
+
+def deleteBuffer(buffer):
+    # --- desktop angle
+    n = 1  
+    buffers = (ctypes.c_uint*n)(buffer)  
+    ()  
+    # --- pyopengl
+    GL.glDeleteBuffers(1, [buffer])
+
+def deleteFramebuffer(framebuffer):
+    # --- desktop angle
+    n = 1  
+    framebuffers = (ctypes.c_uint*n)(framebuffer)  
+    ()
+    # --- pyopengl
+    FBO.glDeleteFramebuffers(1, [framebuffer])
+
+def deleteRenderbuffer(renderbuffer):
+    # --- desktop angle
+    n = 1  
+    renderbuffers = (ctypes.c_uint*n)(renderbuffer)  
+    ()
+    # --- pyopengl
+    FBO.glDeleteRenderbuffers(1, [renderbuffer])
+
+def deleteTexture(texture):
+    # --- desktop angle
+    n = 1  
+    textures = (ctypes.c_uint*n)(texture)  
+    ()
+    # --- pyopengl
+    GL.glDeleteTextures([texture])
+
+
+def createBuffer():
+    # --- desktop angle
+    n = 1
+    buffers = (ctypes.c_uint*n)()
+    ()  
+    return buffers[0]
+    # --- pyopengl
+    return GL.glGenBuffers(1)
+    # --- mock
+    return 1
+
+def createFramebuffer():
+    # --- desktop angle
+    n = 1
+    framebuffers = (ctypes.c_uint*n)()
+    ()
+    return framebuffers[0]
+    # --- pyopengl
+    return FBO.glGenFramebuffers(1)
+    # --- mock
+    return 1
+
+def createRenderbuffer():
+    # --- desktop angle
+    n = 1
+    renderbuffers = (ctypes.c_uint*n)()
+    ()
+    return renderbuffers[0]
+    # --- pyopengl
+    return FBO.glGenRenderbuffers(1)
+    # --- mock
+    return 1
+
+def createTexture():
+    # --- desktop angle
+    n = 1
+    textures = (ctypes.c_uint*n)()
+    ()
+    return textures[0]
+    # --- pyopengl
+    return GL.glGenTextures(1)
+    # --- mock
+    return 1
+
+
+## Image stuff
+
+def texImage2D(target, level, internalformat, format, type, pixels):
+    border = 0
+    # --- desktop angle
+    if isinstance(pixels, (tuple, list)):
+        height, width = pixels
+        pixels = ctypes.c_void_p(0)
+        pixels = None
+    else:
+        if not pixels.flags['C_CONTIGUOUS']:
+            pixels = pixels.copy('C')
+        pixels_ = pixels
+        pixels = pixels_.ctypes.data
+        height, width = pixels_.shape[:2]
+    ()
+    # --- pyopengl
+    if isinstance(pixels, (tuple, list)):
+        height, width = pixels
+        pixels = None
+    else:
+        height, width = pixels.shape[:2]
+    GL.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
+    
+
+
+def texSubImage2D(target, level, xoffset, yoffset, format, type, pixels):
+    # --- desktop angle
+    if not pixels.flags['C_CONTIGUOUS']:
+        pixels = pixels.copy('C')
+    pixels_ = pixels
+    pixels = pixels_.ctypes.data
+    height, width = pixels_.shape[:2]
+    ()
+    # --- pyopengl
+    height, width = pixels.shape[:2]
+    GL.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
+
+
+def readPixels(x, y, width, height, format, type):
+    # --- desktop angle mock
+    # GL_ALPHA, GL_RGB, GL_RGBA
+    t = {6406:1, 6407:3, 6408:4}[format]
+    # we kind of only support type GL_UNSIGNED_BYTE
+    size = int(width*height*t)
+    # --- desktop angle
+    pixels = ctypes.create_string_buffer(size)
+    ()
+    return pixels[:]
+    # --- mock
+    return size * b'\x00'
+
+
+def compressedTexImage2D(target, level, internalformat, width, height, border=0, data=None):
+    # border = 0  # set in args
+    # --- desktop angle
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.size
+    data = data_.ctypes.data
+    ()
+    # --- pyopengl
+    size = data.size
+    GL.glCompressedTexImage2D(target, level, internalformat, width, height, border, size, data)
+
+
+def compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, data):
+    # --- desktop angle
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.size
+    data = data_.ctypes.data
+    ()
+    # --- pyopengl
+    size = data.size
+    GL.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, size, data)
+
+
+## Buffer data
+
+
+def bufferData(target, data, usage):
+    """ Data can be numpy array or the size of data to allocate.
+    """
+    # --- desktop angle
+    if isinstance(data, int):
+        size = data
+        data = ctypes.c_voidp(0)
+    else:
+        if not data.flags['C_CONTIGUOUS'] or not data.flags['ALIGNED']:
+            data = data.copy('C')
+        data_ = data
+        size = data_.nbytes
+        data = data_.ctypes.data
+    ()
+    # --- pyopengl
+    if isinstance(data, int):
+        size = data
+        data = None
+    else:
+        size = data.nbytes
+    GL.glBufferData(target, size, data, usage)
+
+
+def bufferSubData(target, offset, data):
+    # --- desktop angle
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.nbytes
+    data = data_.ctypes.data
+    ()
+    # --- pyopengl
+    size = data.nbytes
+    GL.glBufferSubData(target, offset, size, data)
+
+
+def drawElements(mode, count, type, offset):
+    # --- desktop angle
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, ctypes.c_void_p):
+        pass
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    else:
+        if not offset.flags['C_CONTIGUOUS']:
+            offset = offset.copy('C')
+        offset_ = offset
+        offset = offset.ctypes.data
+    indices = offset
+    ()
+    # --- pyopengl
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    ()
+
+
+def vertexAttribPointer(indx, size, type, normalized, stride, offset):
+    # --- desktop angle
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, ctypes.c_void_p):
+        pass
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    else:
+        if not offset.flags['C_CONTIGUOUS']:
+            offset = offset.copy('C')
+        offset_ = offset
+        offset = offset.ctypes.data
+        # We need to ensure that the data exists at draw time :(
+        # PyOpenGL does this too
+        key = '_vert_attr_'+str(indx)
+        setattr(glVertexAttribPointer, key, offset_)
+    ptr = offset
+    ()
+    # --- pyopengl
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    ()
+
+
+def bindAttribLocation(program, index, name):
+    # --- desktop angle
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    ()
+    # --- pyopengl
+    name = name.encode('utf-8')
+    ()
+
+
+## Setters
+
+
+def shaderSource(shader, source):
+    # Some implementation do not like getting a list of single chars
+    if isinstance(source, (tuple, list)):
+        strings = [s for s in source]
+    else:
+        strings = [source]
+    # --- desktop angle
+    count = len(strings)  
+    string = (ctypes.c_char_p*count)(*[s.encode('utf-8') for s in strings])  
+    length = (ctypes.c_int*count)(*[len(s) for s in strings])  
+    ()
+    # --- pyopengl
+    GL.glShaderSource(shader, strings)
+
+
+## Getters
+
+def _getBooleanv(pname):
+    # --- desktop angle
+    params = (ctypes.c_bool*1)()
+    ()
+    return params[0]
+
+def _getIntegerv(pname):
+    # --- desktop angle
+    n = 16
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*n)(*[d for i in range(n)])
+    ()
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+def _getFloatv(pname):
+    # --- desktop angle
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    ()
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+# def _getString(pname):
+#     # --- desktop angle
+#     ()
+#     return res.value
+#     # --- mock
+#     return ''
+
+
+def getParameter(pname):
+    if pname in [33902, 33901, 32773, 3106, 2931, 2928, 
+                 2849, 32824, 10752, 32938]:
+        # GL_ALIASED_LINE_WIDTH_RANGE GL_ALIASED_POINT_SIZE_RANGE
+        # GL_BLEND_COLOR GL_COLOR_CLEAR_VALUE GL_DEPTH_CLEAR_VALUE
+        # GL_DEPTH_RANGE GL_LINE_WIDTH GL_POLYGON_OFFSET_FACTOR
+        # GL_POLYGON_OFFSET_UNITS GL_SAMPLE_COVERAGE_VALUE
+        return _glGetFloatv(pname)
+    elif pname in [7936, 7937, 7938, 35724, 7939]:
+        # GL_VENDOR, GL_RENDERER, GL_VERSION, GL_SHADING_LANGUAGE_VERSION, 
+        # GL_EXTENSIONS are strings
+        pass  # string handled below
+    else:
+        return _glGetIntegerv(pname)
+    name = pname
+    # --- desktop angle
+    ()
+    return res.decode('utf-8') if res else ''
+    # --- pyopengl
+    res = GL.glGetString(pname)
+    return res.decode('utf-8')
+
+
+def getUniform(program, location):
+    # --- desktop angle
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    ()
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+    # --- pyopengl
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    GL.glGetUniformfv(program, location, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+def getVertexAttrib(index, pname):
+    # --- desktop angle
+    n = 4
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    ()
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+    # --- pyopengl
+    # From PyOpenGL v3.1.0 the glGetVertexAttribfv(index, pname) does
+    # work, but it always returns 4 values, with zeros in the empty
+    # spaces. We have no way to tell whether they are empty or genuine
+    # zeros. Fortunately, pyopengl also supports the old syntax.
+    n = 4
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    GL.glGetVertexAttribfv(index, pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+def getTexParameter(target, pname):
+    # --- desktop angle
+    d = float('Inf')
+    params = (ctypes.c_float*1)(d)
+    ()
+    return params[0]
+
+
+def getActiveAttrib(program, index):
+    # --- desktop angle pyopengl
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    # --- desktop angle
+    ()
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+    # --- pyopengl
+    # pyopengl has a bug, this is a patch
+    GL.glGetActiveAttrib(program, index, bufsize, length, size, type, name)
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+    # --- mock
+    return 'mock_val', 1, 5126
+
+
+def getVertexAttribOffset(index, pname):
+    # --- desktop angle
+    pointer = (ctypes.c_void_p*1)()
+    ()
+    return pointer[0] or 0
+    # --- pyopengl
+    try:  # maybe the fixed it
+        ()
+    except TypeError:
+        pointer = (ctypes.c_void_p*1)()
+        GL.glGetVertexAttribPointerv(index, pname, pointer)
+        return pointer[0] or 0
+    # --- mock
+    return 0
+
+    
+def getActiveUniform(program, index):
+    # --- desktop angle
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    ()
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+    # --- pyopengl
+    name, size, type = GL.glGetActiveUniform(program, index)
+    return name.decode('utf-8'), size, type
+
+
+def getAttachedShaders(program):
+    # --- desktop angle
+    maxcount = 256
+    count = (ctypes.c_int*1)()
+    shaders = (ctypes.c_uint*maxcount)()
+    ()
+    return tuple(shaders[:count[0]])
+
+
+def getAttribLocation(program, name):
+    # --- desktop angle
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    ()
+    return res
+    # --- pyopengl
+    name = name.encode('utf-8')
+    ()
+    
+
+def getUniformLocation(program, name):
+    # --- desktop angle
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    ()
+    return res
+    # --- pyopengl
+    name = name.encode('utf-8')
+    ()
+
+def getProgramInfoLog(program):
+    # --- desktop angle
+    bufsize = 1024
+    length = (ctypes.c_int*1)()
+    infolog = ctypes.create_string_buffer(bufsize)
+    ()
+    return infolog[:length[0]].decode('utf-8')
+    # --- pyopengl
+    res = GL.glGetProgramInfoLog(program)
+    return res.decode('utf-8')
+
+def getShaderInfoLog(shader):
+    # --- desktop angle
+    bufsize = 1024
+    length = (ctypes.c_int*1)()
+    infolog = ctypes.create_string_buffer(bufsize)
+    ()
+    return infolog[:length[0]].decode('utf-8')
+    # --- pyopengl
+    res = GL.glGetShaderInfoLog(shader)
+    return res.decode('utf-8')
+
+def getProgramParameter(program, pname):
+    # --- desktop angle
+    params = (ctypes.c_int*1)()
+    ()
+    return params[0]
+
+def getShaderParameter(shader, pname):
+    # --- desktop angle
+    params = (ctypes.c_int*1)()
+    ()
+    return params[0]
+
+def getShaderPrecisionFormat(shadertype, precisiontype):
+    # --- desktop angle
+    range = (ctypes.c_int*1)()
+    precision = (ctypes.c_int*1)()
+    ()
+    return range[0], precision[0]
+
+def getShaderSource(shader):
+    # --- desktop angle
+    bufsize = 1024*1024
+    length = (ctypes.c_int*1)()
+    source = (ctypes.c_char*bufsize)()
+    ()
+    return source.value[:length[0]].decode('utf-8')
+    # --- pyopengl
+    res = GL.glGetShaderSource(shader)
+    return res.decode('utf-8')
+    
+
+def getBufferParameter(target, pname):
+    # --- desktop angle
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    ()
+    return params[0]
+
+
+def getFramebufferAttachmentParameter(target, attachment, pname):
+    # --- desktop angle
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    ()
+    return params[0]
+    # --- pyopengl
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    FBO.glGetFramebufferAttachmentParameteriv(target, attachment, pname, params)
+    return params[0]
+
+
+def getRenderbufferParameter(target, pname):
+    # --- desktop angle
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    ()
+    return params[0]
+    # --- pyopengl
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    FBO.glGetRenderbufferParameteriv(target, pname, params)
+    return params[0]
+
+
+
+## ============================================================================
+
+
+class FunctionAnnotation:
+    def __init__(self, name, args, output):
+        self.name = name
+        self.args = args
+        self.output = output
+        self.lines = []  # (line, comment) tuples
+    
+    def __repr__(self):
+        return '<FunctionAnnotation for %s>' % self.name
+        
+    def get_lines(self, call, backend):
+        """ Get the lines for this function based on the given backend. 
+        The given API call is inserted at the correct location.
+        """
+        backend_selector = backend  # first lines are for all backends
+        lines = []
+        for line in self.lines:
+            if line.lstrip().startswith('# ---'):
+                backend_selector = line
+                continue
+            if backend in backend_selector:
+                if line.strip() == '()':
+                    indent = line.split('(')[0][4:]
+                    line = indent + call
+                lines.append(line)
+        return lines
+    
+    def is_arg_set(self, name):
+        """ Get whether a given variable name is set.
+        This allows checking whether a variable that is an input to the C
+        function is not an input for the Python function, and may be an output.
+        """
+        needle = '%s =' % name
+        for line, comment in self.lines:
+            if line.startswith(needle):
+                return True
+        else:
+            return False
+
+
+
+def parse_anotations():
+    """ Parse this annotations file and produce a dictionary of
+    FunctionAnnotation objects.
+    """
+    
+    functions = {}
+    function = None
+    
+    for line in open(__file__, 'rt').readlines():
+        # Stop?
+        if '='*40 in line:
+            break
+        
+        if line.startswith('def '):
+            name = line.split(' ')[1].split('(')[0]
+            args = line.split('(')[1].split(')')[0].split(', ')
+            args = [arg for arg in args if arg]
+            out =  line.partition('->')[2].strip()
+            function = FunctionAnnotation(name, args, out)
+            functions[name] = function
+            continue
+        elif not function:
+            continue
+        
+        # Add line
+        line = line.rstrip()
+        indent = len(line) - len(line.strip())
+        if line.strip() and indent >=4:
+            function.lines.append(line)
+
+    return functions
+
+
+if __name__ == '__main__':
+    print(parse_anotations().keys())
+    
\ No newline at end of file
diff --git a/codegen/createglapi.py b/codegen/createglapi.py
index 1e6e7e3..f08be8d 100644
--- a/codegen/createglapi.py
+++ b/codegen/createglapi.py
@@ -1,35 +1,55 @@
+#!/usr/bin/env python3
 # -*- 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
+"""
+Scipt to generate code for our gl API, including all its backends.
+
+The files involved in the code generation process are:
+
+  * gl2.h - the C header file for the GL ES 2.0 API
+  * webgl.idl - the interface definition language for WebGL
+  * annotations.py - manual annotations for non-trivial functions
+  * headerparser.py - parses .h and .idl files 
+  * createglapi.py - uses all of the above to generate the actual code
+
+Rationale
+---------
 
-The header file for the OpenGL ES standard is parsed to obtain a list
-of constants and functions.
+The GL ES 2.0 API is relatively small. Therefore a fully automated build
+process like PyOpenGL now has is not really necessary. I tried to
+automate what we can and simply do the rest with manual annotations.
+Some groups of functions (like glUniform and friends) are handled in
+*this* file.
 
-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.
+Even though the API is quite small, we want to generate several
+implementations, such as desktop, Angle, a generic proxy, webgl, a mock
+backend and possibly more. Therefore automation is crucial.
+
+Further notes
+-------------
+
+This file is pretty big and even though I tried to make the code as clear
+as possible, it's not always that easy to read. In effect this code is
+not so easy to maintain. I hope it is at least clear enough so it can be
+used to maintain the GL API itself.
 
 """
 
-import os 
+import os
 import sys
+import ctypes  # not actually used, but handy to have imported during dev
 
-from OpenGL import GL
+import headerparser
+from annotations import parse_anotations
+
+from OpenGL import GL  # For checking
 
 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 = '''""" 
+PREAMBLE = '''"""
 
 THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
 
@@ -38,103 +58,726 @@ THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
 """
 '''
 
+## Create parsers
+
+# Create a parser for desktop and web gl
+parser1 = headerparser.Parser(os.path.join(THISDIR, 'headers', 'gl2.h'))
+headerparser.CONSTANTS = {}
+parser2 = headerparser.Parser(os.path.join(THISDIR, 'headers', 'webgl.idl'))
+
+# Get annotations
+annotations = parse_anotations()
+
+
+## Check constants and generate API module
+
+# Get names
+names1 = set(parser1.constant_names)
+names2 = set(parser2.constant_names)
+
+# Check names correspondence
+if names1 == names2:
+    print('Constants in gl2 and webgl are equal')
+else:
+    print('===== Extra names in gl2 =====')
+    print(', '.join(names1.difference(names2)))
+    print('===== Extra names in webgl =====')
+    print(', '.join(names2.difference(names1)))
+    print('===========')
+
+# Test value correspondence
+superset = names1.intersection(names2)
+#
+constants = {}
+for c1 in parser1.constants.values():
+    if c1.shortname in superset:
+        constants[c1.shortname] = c1.value
+#
+assert len(constants) == len(superset)
+#
+for c2 in parser2.constants.values():
+    if c2.shortname in constants:
+        assert c2.value == constants[c2.shortname]
+print('Hooray! All constants that occur in both namespaces have equal values.')
+
+
+DEFINE_ENUM = """
+class Enum(int):
+    ''' Enum (integer) with a meaningfull repr. '''
+    def __new__(cls, name, value):
+        base = int.__new__(cls, value)
+        base.name = name
+        return base
+    def __repr__(self):
+        return self.name
+"""
+
+DEFINE_CONST_MAP = """
+ENUM_MAP = {}
+for ob in list(globals().values()):
+    if repr(ob).startswith('GL_'):
+        ENUM_MAP[int(ob)] = ob
+del ob
+"""
 
 def create_constants_module(parser, extension=False):
-    
-    # Initialize 
+
+    # 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')
+    # Define ENUM
+    lines.append(DEFINE_ENUM)
+    lines.append('')
     
     # 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('_')
+        for c in parser.constants.values():
+            if 'OES' in c.oname:
+                c.oname = c.oname.replace('OES','')
+                c.oname = c.oname.replace('__','_').strip('_')
                 constantDefs.append(c)
     else:
-        constantDefs = parser.constantDefs
-    
+        constantDefs = parser.constants.values()
+
     # Insert constants
-    for c in sorted(constantDefs, key=lambda x:x.cname):
+    for c in sorted(constantDefs, key=lambda x: x.oname):
         if isinstance(c.value, int):
-            lines.append('%s = _GL_ENUM(%r, %r)' % (c.cname, c.cname, c.value))
+            lines.append('%s = Enum(%r, %r)' % (c.oname, c.oname, c.value))
         else:
-            lines.append('%s = %r' % (c.cname, c.value))
+            lines.append('%s = %r' % (c.oname, c.value))
     lines.append('')
     
+    lines.append(DEFINE_CONST_MAP)
+    
     # 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))
+    with open(os.path.join(GLDIR, fname), 'wb') as f:
+        f.write(('\n'.join(lines)).encode('utf-8'))
     print('wrote %s' % fname)
+    
+
+create_constants_module(parser1)
+
+
+## List functions
 
+IGNORE_FUNCTIONS = ['releaseShaderCompiler', 'shaderBinary']
 
-def create_desktop_module(parser, extension=False):
+WEBGL_EQUIVALENTS = {   
+    'genBuffers': 'createBuffer',
+    'genFramebuffers': 'createFramebuffer',
+    'genRenderbuffers': 'createRenderbuffer',
+    'genTextures': 'createTexture',
     
-    # Initialize
-    lines = []
-    doc = 'OpenGL ES 2.0 API based on desktop OpenGL (via pyOpenGL).'
-    lines.append(PREAMBLE % doc)
+    'deleteBuffers': 'deleteBuffer',
+    'deleteFramebuffers': 'deleteFramebuffer',
+    'deleteRenderbuffers': 'deleteRenderbuffer',
+    'deleteTextures': 'deleteTexture',
     
-    # __future__ import
-    lines.append('from __future__ import print_function, division, absolute_import\n')
+    'clearDepthf': 'clearDepth',
+    'depthRangef': 'depthRange',
     
-    # Import constants and ext
-    if extension:
-        lines.append('from ._constants_ext import *')
-    else:
-        lines.append('from ._constants import *')
+    'getBufferParameteriv': 'getBufferParameter',
+    'getRenderbufferParameteriv': 'getRenderbufferParameter',
+    'getFramebufferAttachmentParameteriv': 'getFramebufferAttachmentParameter',
     
-    lines.append('\n')
+    'getVertexAttribPointerv': 'getVertexAttribOffset',
     
-    # 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)
+    'getProgramiv': 'getProgramParameter',
+    'getShaderiv': 'getShaderParameter',
+    
+    'getBooleanv': 'getParameter',
+    'getFloatv': 'getParameter',
+    'getIntegerv': 'getParameter',
+    'getString': 'getParameter',  # getParameter is getString + getFloat
+    }
+
+# Types that we convert easily from Python to C (and back)
+EASY_TYPES = {  'void': (type(None), 'c_voidp'),  # only for output
+                'GLenum': (int, 'c_uint'),
+                'GLboolean': (bool, 'c_bool'),
+                'GLuint': (int, 'c_uint'),
+                'GLint': (int, 'c_int'),
+                'GLbitfield': (int, 'c_uint'),
+                'GLsizei': (int, 'c_int'),
+                'GLfloat': (float, 'c_float'),
+                'GLclampf': (float, 'c_float'),
+             }
+
+# Types that dont map 1-on-1 to Python values, but that we know
+# how to set the ctypes argtypes for. 
+HARDER_TYPES = {
+                'GLenum*':('', 'POINTER(ctypes.c_uint)'),
+                'GLboolean*':('', 'POINTER(ctypes.c_bool)'),
+                'GLuint*':('', 'POINTER(ctypes.c_uint)'),
+                'GLint*':('', 'POINTER(ctypes.c_int)'),
+                'GLsizei*':('', 'POINTER(ctypes.c_int)'),
+                'GLfloat*':('', 'POINTER(ctypes.c_float)'),
+                
+                'GLubyte*':('', 'c_char_p'),
+                'GLchar*':('', 'c_char_p'),
+                'GLchar**':('', 'POINTER(ctypes.c_char_p)'),
+                'GLvoid*':('', 'c_void_p'),  # or c_voidp?
+                'GLvoid**':('', 'POINTER(ctypes.c_void_p)'),
+                'GLintptr':('', 'c_int'), 
+                'GLsizeiptr':('', 'c_int'),
+                }
+
+# Together the EASY_TYPES and HARDER_TYPES should cover all types that
+# ES 2.0 uses.
+KNOWN_TYPES = EASY_TYPES.copy()
+KNOWN_TYPES.update(HARDER_TYPES)
+
+
+def apiname(funcname):
+    """ Define what name the API uses, the short or the gl version.
+    """
+    if funcname.startswith('gl'):
+        return funcname
     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)
+        if funcname.startswith('_'):
+            return '_gl' + funcname[1].upper() + funcname[2:]
         else:
-            print('WARNING: %s seems not available in PyOpenGL' % f.cname)
-    lines.append('    ]')
+            return 'gl' + funcname[0].upper() + funcname[1:]
+
+
+class FunctionDescription:
+    def __init__(self, name, es2func, wglfunc, annfunc):
+        self.name = name
+        self.apiname = apiname(name)
+        self.es2 = es2func
+        self.wgl = wglfunc
+        self.ann = annfunc
+        self.args = []
+
+
+# Keep track of what webGL names we "used"
+used_webgl_names = set()
+
+# Also keep track of what functions we could handle automatically, 
+# and which not. Just for reporting.
+functions_auto = set()
+functions_anno = set()
+functions_todo = set()
+
+
+def combine_function_definitions():
+    """ Process function definitions of ES 2.0, WebGL and annotations.
+    We try to combine information from these three sources to find the
+    arguments for the Python API. In this "merging" process we also
+    check for inconsistencies between the API definitions.
+    """
+    functions = []
+    have_getParameter = False
     
-    lines.append('')
+    for name in parser1.function_names:
+        if name in IGNORE_FUNCTIONS:
+            continue
+        
+        # Get es2 function
+        es2func = parser1.functions[name]
+        
+        # Get webgl version
+        lookupname = WEBGL_EQUIVALENTS.get(es2func.shortname, es2func.shortname)
+        wglfunc = parser2.functions.get(lookupname, None)
+        if wglfunc:
+            used_webgl_names.add(lookupname)
+        else:
+            print('WARNING: %s not available in WebGL' % es2func.shortname)
+        
+        # Convert name
+        name = WEBGL_EQUIVALENTS.get(name, name)
+        
+        # Avoid duplicates for getParameter
+        if name == 'getParameter':
+            if es2func.shortname != 'getString':
+                name =  '_' + es2func.shortname
+        
+        # Get annotated version
+        annfunc = annotations.get(name, None)
+        
+        # Create description instance
+        des = FunctionDescription(name, es2func, wglfunc, annfunc)
+        functions.append(des)
+        
+        # Get information about arguments
+        if True:
+            argnames_es2 = [arg.name for arg in es2func.args[1:]]
+        if wglfunc:
+            argnames_wgl = [arg.name for arg in wglfunc.args[1:]]
+        if annfunc:
+            argnames_ann = annfunc.args  # Can contain 'argname=default'
+            argnames_ann = [arg.split('=')[0] for arg in argnames_ann]
+        
+        # Set argumenets specification of our GL API
+        # Also check and report when we deviate from the WebGL API
+        if wglfunc and argnames_es2 == argnames_wgl:
+            if annfunc and argnames_ann != argnames_es2:
+                des.args = argnames_ann
+                print('WARNING: %s: Annotation overload even though webgl and es2 match.'%name)
+            else:
+                des.args = argnames_es2
+        elif wglfunc:
+            if annfunc and argnames_ann != argnames_wgl:
+                des.args = argnames_ann
+                print('WARNING: %s: Annotation overload webgl args.'%name)
+            else:
+                #print('WARNING: %s: assuming wgl args.'%name)
+                des.args = argnames_wgl
+        else:
+            print('WARNING: %s: Could not determine args!!'%name)
+        
+        # Go over all functions to test if they are in OpenGL
+        for func in [es2func, wglfunc]:
+            if func is None: 
+                continue
+            group = func.group or [func]
+            for f in group:
+                # Check opengl
+                if f.oname.startswith('gl') and not hasattr(GL, f.glname):
+                    print('WARNING: %s seems not available in PyOpenGL' % f.glname)
     
-    # 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)
+    return functions
+
+
+## Get full function definitions and report
+# Get functions
+functions = combine_function_definitions()
 
+# Check which WebGL functions we did not find/use
+for name in set(parser2.function_names).difference(used_webgl_names):
+    print('WARNING: WebGL function %s not in Desktop' % name)
 
-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)
+# Report status
+print('Could generate %i functions automatically, and %i with annotations' %
+      (len(functions_auto), len(functions_anno)))
+print('Need more info for %i functions.' % len(functions_todo))
+if not functions_todo:
+    print('Hooray! All %i functions are covered!' % len(functions))
+
+
+
+## Define generators
+
+class ApiGenerator:
+    """ Base API generator class. We derive several subclasses to implement
+    the different backends.
+    """
+    
+    DESCRIPTION = "GL API X"
+    PREAMBLE = ""
+    
+    def __init__(self):
+        self.lines = []
+    
+    def save(self):
+        # Remove too many whitespace
+        text = '\n'.join(self.lines) + '\n'
+        for i in range(10):
+            text = text.replace('\n\n\n\n', '\n\n\n')
+        # Write
+        with open(self.filename, 'wb') as f:
+            f.write((PREAMBLE % self.DESCRIPTION).encode('utf-8'))
+            for line in self.PREAMBLE.splitlines():
+                f.write(line[4:].encode('utf-8')+b'\n')
+            f.write(b'\n')
+            f.write(text.encode('utf-8'))
+    
+    def add_function(self, des):
+        if des.es2.group:
+            if des.name.startswith('get'):
+                assert len(des.es2.group) == 2  # vi and fv
+                des.es2 = des.es2.group[0]  # f comes before 1
+                self._add_function(des)
+            else:
+                self._add_function_group(des)
+        else:
+            self._add_function(des)
+        self.lines.append('\n')  # two lines between each function
+    
+    
+    def _add_function_group(self, des):
+        lines = self.lines
+        handled = True
+        
+        # Create map to es2 function objects
+        es2funcs = {}
+        for f in des.es2.group:
+            cname = f.shortname
+            es2funcs[cname] = f
+        
+        if des.name == 'uniform':
+            for t in ('float', 'int'):
+                for i in (1,2,3,4):
+                    args = ', '.join(['v%i'%j for j in range(1,i+1)])
+                    cname = 'uniform%i%s' % (i, t[0])
+                    sig = '%s(location, %s)' % (apiname(cname), args)
+                    self._add_group_function(des, sig, es2funcs[cname])
+            for t in ('float', 'int'):
+                for i in (1,2,3,4):
+                    cname = 'uniform%i%sv' % (i, t[0])
+                    sig = '%s(location, count, values)' % apiname(cname)
+                    self._add_group_function(des, sig, es2funcs[cname])
+        elif des.name == 'uniformMatrix':
+            for i in (2,3,4):
+                cname = 'uniformMatrix%ifv' % i
+                sig = '%s(location, count, transpose, values)' % apiname(cname)
+                self._add_group_function(des, sig, es2funcs[cname])
+        elif des.name == 'vertexAttrib':
+            for i in (1,2,3,4):
+                args = ', '.join(['v%i'%j for j in range(1,i+1)])
+                cname = 'vertexAttrib%if' % i
+                sig = '%s(index, %s)' % (apiname(cname), args)
+                self._add_group_function(des, sig, es2funcs[cname])
+        elif des.name == 'texParameter':
+            for t in ('float', 'int'):
+                cname = 'texParameter%s' % t[0]
+                sig = '%s(target, pname, param)' % apiname(cname)
+                self._add_group_function(des, sig, es2funcs[cname])
+        else:
+            handled = False
+        
+        if handled:
+            functions_auto.add(des.name)
+        else:
+            functions_todo.add(des.name)
+            lines.append('# todo: Dont know group %s' % des.name)
+    
+    def _add_function(self, des):
+        # Need to be overloaded in subclass
+        raise NotImplementedError()
+    
+    def _add_group_function(self, des, sig, es2func):
+        # Need to be overloaded in subclass
+        raise NotImplementedError()
+
+
+
+class ProxyApiGenerator(ApiGenerator):
+    """ Generator for the general proxy class that will be loaded into gloo.gl.
+    """
+    
+    filename = os.path.join(GLDIR, '_proxy.py')
+    DESCRIPTION = 'Base proxy API for GL ES 2.0.'
+    PREAMBLE = '''
+    class BaseGLProxy(object):
+        """ Base proxy class for the GL ES 2.0 API. Subclasses should
+        implement __call__ to process the API calls.
+        """
+       
+        def __call__(self, funcname, returns, *args):
+            raise NotImplementedError()
+        
+        
+        def glShaderSource_compat(self, handle, code):
+            return self("glShaderSource_compat", True, handle, code)
+    '''
+    
+    def _returns(self, des):
+        shortame = des.name
+        for prefix in ("get", "is", "check", "create", "read"):
+            if shortame.startswith(prefix):
+                return True
+        else:
+            return False
+    
+    
+    def _add_function(self, des):
+        ret = self._returns(des)
+        prefix = 'return ' if ret else ''
+        argstr = ', '.join(des.args)
+        self.lines.append('    def %s(self, %s):' % (des.apiname, argstr))
+        self.lines.append('        %sself("%s", %r, %s)' % 
+                          (prefix, apiname(des.name),ret, argstr))
     
-    # Create code for extensions
-    parser = Parser(os.path.join(THISDIR, 'headers', 'gl2ext.h'))
-    create_constants_module(parser, True)
-    create_desktop_module(parser, True)
+    def _add_group_function(self, des, sig, es2func):
+        ret = self._returns(des)
+        prefix = 'return ' if ret else ''
+        funcname = apiname(sig.split('(')[0])
+        args = sig.split('(', 1)[1].split(')')[0]
+        #self.lines.append('    def %s:' % sig)
+        self.lines.append('    def %s(self, %s):' % (funcname, args))
+        self.lines.append('        %sself("%s", %r, %s)' % 
+                          (prefix, funcname, ret, args))
+
+
+
+class DesktopApiGenerator(ApiGenerator):
+    """ Generator for the desktop GL backend.
+    """
+    
+    filename = os.path.join(GLDIR, '_desktop.py')
+    write_c_sig = True
+    define_argtypes_in_module = False
+    
+    DESCRIPTION = "Subset of desktop GL API compatible with GL ES 2.0"
+    PREAMBLE = """
+    import ctypes
+    from .desktop import _lib, _get_gl_func
+    """
+    
+    def _get_argtype_str(self, es2func):
+        ce_arg_types = [arg.ctype for arg in es2func.args[1:]]
+        ct_arg_types = [KNOWN_TYPES.get(arg.ctype, None) for arg in es2func.args]
+        # Set argument types on ctypes function
+        if None in ct_arg_types:
+            argstr = 'UNKNOWN_ARGTYPES'
+        elif es2func.group:
+            argstr = 'UNKNOWN_ARGTYPES'
+        else:
+            argstr = ', '.join(['ctypes.%s' % t[1] for t in ct_arg_types[1:]])
+            argstr = '()' if not argstr else '(%s,)' % argstr
+        # Set output arg (if available)
+        if ct_arg_types[0][0] != type(None):
+            resstr = 'ctypes.%s' % ct_arg_types[0][1]
+        else:
+            resstr = 'None'
+        return resstr, argstr
+    
+    
+    def _write_argtypes(self, es2func):
+        lines = self.lines
+        ce_arg_types = [arg.ctype for arg in es2func.args[1:]]
+        ct_arg_types = [KNOWN_TYPES.get(arg.ctype, None) for arg in es2func.args]
+        # Set argument types on ctypes function
+        if None in ct_arg_types:
+            lines.append('# todo: unknown argtypes')
+        elif es2func.group:
+            lines.append('# todo: oops, dont set argtypes for group!')
+        else:
+            if ct_arg_types[1:]:
+                argstr = ', '.join(['ctypes.%s' % t[1] for t in ct_arg_types[1:]])
+                lines.append('_lib.%s.argtypes = %s,' % (es2func.glname, argstr))
+            else:
+                lines.append('_lib.%s.argtypes = ()' % es2func.glname)
+        # Set output arg (if available)
+        if ct_arg_types[0][0] != type(None):
+            lines.append('_lib.%s.restype = ctypes.%s' % (es2func.glname, ct_arg_types[0][1]))
+    
+    def _native_call_line(self, name, es2func, cargstr=None, prefix='', indent=4):
+        #'_lib.%s(%s)' % (des.es2.glname, cargstr)
+        resstr, argstr = self._get_argtype_str(es2func)
+        if cargstr is None:
+            cargs = [arg.name for arg in es2func.args[1:]]
+            cargstr = ', '.join(cargs)
+        
+        lines = 'try:\n'
+        lines += '    nativefunc = %s._native\n' % apiname(name)
+        lines += 'except AttributeError:\n'
+        lines += '    nativefunc = %s._native = _get_gl_func("%s", %s, %s)\n' % (
+                apiname(name), es2func.glname, resstr, argstr)
+        lines += '%snativefunc(%s)\n' % (prefix, cargstr)
+        
+        #lines += 'check_error("%s")' % name
+        
+        lines = [' '*indent + line for line in lines.splitlines()]
+        return '\n'.join(lines)
+        
+    
+    def _add_function(self, des):
+        lines = self.lines
+        es2func = des.es2
+        
+        # Write arg types
+        if self.define_argtypes_in_module:
+            self._write_argtypes(es2func)
+        
+        # Get names and types of C-API
+        ce_arg_types = [arg.ctype for arg in es2func.args[1:]]
+        ce_arg_names = [arg.name for arg in es2func.args[1:]]
+        ct_arg_types = [KNOWN_TYPES.get(arg.ctype, None) for arg in es2func.args]
+        ct_arg_types_easy = [EASY_TYPES.get(arg.ctype, None) for arg in es2func.args]
+        
+        # Write C function signature, for debugging and development
+        if self.write_c_sig:
+            argnamesstr = ', '.join([c_type+' '+c_name for c_type, c_name in zip(ce_arg_types, ce_arg_names)])
+            lines.append('# %s = %s(%s)' % (es2func.args[0].ctype, es2func.oname, argnamesstr))
+        
+        # Write Python function def
+        lines.append('def %s(%s):' % (des.apiname,  ', '.join(des.args)))
+        
+        # Construct C function call
+        cargs = [arg.name for arg in des.es2.args[1:]]
+        cargstr = ', '.join(cargs)
+        #callline = '_lib.%s(%s)' % (des.es2.glname, cargstr)
+        
+        # Now write the body of the function ...
+        if des.ann:
+            prefix = 'res = '
+            # Annotation available
+            functions_anno.add(des.name)
+            callline = self._native_call_line(des.name, es2func, prefix=prefix)
+            lines.extend( des.ann.get_lines(callline, 'desktop') )
+        
+        elif es2func.group:
+            # Group?
+            functions_todo.add(des.name)
+            lines.append('    pass  # todo: Oops. this is a group!')
+        elif None in ct_arg_types_easy:
+            functions_todo.add(des.name)
+            lines.append('    pass  # todo: Not all easy types!')
+        elif des.args != [arg.name for arg in des.wgl.args[1:]]:
+            functions_todo.add(des.name)
+            lines.append('    pass  # todo: ES 2.0 and WebGL args do not match!')
+        else:
+            # This one is easy!
+            functions_auto.add(des.name)
+            # Get prefix
+            prefix = ''
+            if ct_arg_types[0][0] != type(None):
+                prefix = 'return '
+            elif des.es2.shortname.startswith('get'):
+                raise RuntimeError('Get func returns void?')
+            # Set string
+            callline = self._native_call_line(des.name, des.es2, prefix=prefix)
+            lines.append(callline)
+        
+        
+        if 'desktop' in self.__class__.__name__.lower():
+            # Post-fix special cases for desktop gl. See discussion in #201
+            # glDepthRangef and glClearDepthf are not always available,
+            # and sometimes they do not work if they are
+            if es2func.oname in ('glDepthRangef', 'glClearDepthf'):
+                for i in range(1,10):
+                    line = lines[-i]
+                    if not line.strip() or line.startswith('#'):
+                        break
+                    line = line.replace('c_float', 'c_double')
+                    line = line.replace('glDepthRangef', 'glDepthRange')
+                    line = line.replace('glClearDepthf', 'glClearDepth')
+                    lines[-i] = line
+    
+    
+    def _add_group_function(self, des, sig, es2func):
+        lines = self.lines
+        handled = True
+        
+        call_line = self._native_call_line
+        
+        if self.define_argtypes_in_module:
+            self._write_argtypes(es2func)
+        
+        funcname = sig.split('(', 1)[0]
+        args = sig.split('(', 1)[1].split(')')[0]
+        cfuncname = 'gl' + funcname[0].upper() + funcname[1:]
+        
+        if des.name == 'uniform':
+            if funcname[-1] != 'v':
+                lines.append('def %s:' % sig)
+                lines.append(call_line(funcname, es2func, args))
+            else:
+                t = {'f':'float', 'i':'int'}[funcname[-2]]
+                lines.append('def %s:' % sig)
+                lines.append('    values = [%s(val) for val in values]' % t)
+                lines.append('    values = (ctypes.c_%s*len(values))(*values)' % t)
+                lines.append(call_line(funcname, es2func, 'location, count, values'))
+        elif des.name == 'uniformMatrix':
+            lines.append('def %s:' % sig)
+            lines.append('    if not values.flags["C_CONTIGUOUS"]:')
+            lines.append('        values = values.copy()')
+            lines.append('    assert values.dtype.name == "float32"')
+            lines.append('    values_ = values')
+            lines.append('    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))')
+            lines.append(call_line(funcname, es2func, 'location, count, transpose, values'))
+        elif des.name == 'vertexAttrib':
+            lines.append('def %s:' % sig)
+            lines.append(call_line(funcname, es2func, args))
+        elif des.name == 'texParameter':
+            lines.append('def %s:' % sig)
+            lines.append(call_line(funcname, es2func, args))
+        
+        else:
+            raise ValueError('unknown group func')
+
+
+class AngleApiGenrator(DesktopApiGenerator):
+    """ Generator for the Angle backend (GL to Directx conversion on
+    Windows). Very similar to the desktop API, but we do not need that
+    deferred loading of GL functions here.
+    """
+    
+    filename = os.path.join(GLDIR, '_angle.py')
+    write_c_sig = True
+    define_argtypes_in_module = True
+    
+    DESCRIPTION = "GL ES 2.0 API based on the Angle library (i.e. DirectX)"
+    PREAMBLE = """
+    import ctypes
+    from .angle import _lib
+    """
+    
+    def _native_call_line(self, name, es2func, cargstr=None, prefix='', indent=4):
+        resstr, argstr = self._get_argtype_str(es2func)
+        if cargstr is None:
+            cargs = [arg.name for arg in es2func.args[1:]]
+            cargstr = ', '.join(cargs)
+        return ' '*indent + '%s_lib.%s(%s)' % (prefix, es2func.glname, cargstr)
+
+
+
+class PyOpenGLApiGenrator(ApiGenerator):
+    """ Generator for a fallback pyopengl backend.
+    """
+    
+    filename = os.path.join(GLDIR, '_pyopengl.py')
+    DESCRIPTION = 'Proxy API for GL ES 2.0 subset, via the PyOpenGL library.'
+    PREAMBLE = """
+    import ctypes
+    from OpenGL import GL
+    import OpenGL.GL.framebufferobjects as FBO
+    """
+    
+    def __init__(self):
+        ApiGenerator.__init__(self)
+        self._functions_to_import = []
+
+    def _add_function(self, des):
+        # Fix for FBO?
+        mod = 'GL'
+        if 'renderbuffer' in des.name.lower() or 'framebuffer' in des.name.lower():
+            mod = 'FBO'
+        # Get call line
+        argstr = ', '.join(des.args)
+        call_line = '    return %s.%s(%s)' % (mod, des.es2.glname, argstr)
+        # Get annotation lines
+        ann_lines = []
+        if des.ann is not None:
+            ann_lines = des.ann.get_lines(call_line, 'pyopengl')
+        # Use annotation or not
+        if ann_lines:
+            self.lines.append('def %s(%s):' % (des.apiname, argstr))
+            self.lines.extend(ann_lines)
+        else:
+            # To be imported from OpenGL.GL
+            self._functions_to_import.append((des.es2.glname, des.apiname))
+    
+    def _add_group_function(self, des, sig, es2func):
+        # All group functions can be directly imported from OpenGL
+        funcname = apiname(sig.split('(')[0])
+        self._functions_to_import.append((funcname, funcname))
+    
+    def save(self):
+        # Write remaining functions
+        self.lines.append('# List of functions that we should import from OpenGL.GL')
+        self.lines.append('_functions_to_import = [')
+        for name1, name2 in self._functions_to_import:
+            self.lines.append('    ("%s", "%s"),' % (name1, name2))
+        self.lines.append('    ]')
+        
+        # Really save
+        ApiGenerator.save(self)
+
+
+## Generate
+
+# Generate
+for Gen in [ProxyApiGenerator, DesktopApiGenerator, AngleApiGenrator, 
+            PyOpenGLApiGenrator]:
+    gen = Gen()
+    for des in functions:
+        gen.add_function(des)
+    gen.save()
+
+
diff --git a/codegen/headerparser.py b/codegen/headerparser.py
index 9ec52c0..3c74d0b 100644
--- a/codegen/headerparser.py
+++ b/codegen/headerparser.py
@@ -2,18 +2,15 @@
 # Copyright (c) 2013, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
-""" Code to parse a header file.
+""" Code to parse a header file and create a list of constants,
+functions (with arguments). This information can then be used to
+autogenerate our OpenGL API.
 """
 
 import os
 import sys
 
 
-TYPEMAP = {
-    'GLenum': 'int (GLenum)',
-    }
-
-
 def getwords(line):
     """ Get words on a line.
     """
@@ -21,51 +18,53 @@ def getwords(line):
     return [w for w in line.split(' ') if w]
 
 
-# Keep track of all constants in case they are "reused" in a header file
+# Keep track of all constants in case they are "reused" (i.e. aliases)
 CONSTANTS = {}
 
 
 class Parser:
-    """ Class to parse header files.
+    """ Class to parse header files. It can deal with gl2.h and webgl.idl,
+    as well as some desktop OpenGL header files. It produces a list of
+    ConstantDefition objects and FunctionDefinition objects, which can 
+    be accessed via a dict.
     """
-    
+
     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]
-        
+        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 = []
-        
+        self._c_filename = header_file
+
+        # Init intermediate results
+        self._functionDefs = []
+        self._constantDefs = []
+
+        # Init output
+        self._functions = {}
+        self._constants = {}
+
         # 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)
+            self._functionDefs.append(definition)
         elif isinstance(definition, ConstantDefinition):
-            self.constantDefs.append(definition)
-        
+            self._constantDefs.append(definition)
         return self
-    
-    
+
     def _get_line(self):
         # Get a stripped line, and keep track of line nr, skip empty lines
         line = ''
@@ -76,263 +75,295 @@ class Parser:
             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') ):
+            elif line.startswith('const GLenum'):
+                self += ConstantDefinition(line)
+            elif '(' in line:
                 while ')' not in line:
                     line += self._get_line()
-                #self += self.handle_function(line)
-                self += FunctionDefinition(line)
-        
+                if line.endswith(');'):
+                    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)
+        self._functionDefs = [d for d in self._functionDefs if d.isvalid]
+        self._constantDefs = [d for d in self._constantDefs if d.isvalid]
+
+        # Collect multipe similar functions in groups
+        self._functionDefs.sort(key=lambda x: x.glname)
         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
+        keyDefs = []
+        for funcDef in [f for f in self._functionDefs]:
+            # Check if we need a new keydef
             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()
-        
+                # Create new keydef or use old one?
+                if keyDef and keyDef.glname == funcDef.keyname:
+                    pass  # Keep same keydef
+                else:
+                    keyDef = FunctionGroup(funcDef.line)  # New keydef
+                    keyDef._set_name(funcDef.keyname)
+                    keyDefs.append(keyDef)
+                # Add to group
+                keyDef.group.append(funcDef)
+        # Process function groups
+        for keyDef in keyDefs:
+            if len(keyDef.group) > 1:
+                self._functionDefs.append(keyDef)
+                for d in keyDef.group:
+                    self._functionDefs.remove(d)
+
+        # Sort constants and functions
+        self._functionDefs.sort(key=lambda x: x.glname)
+        self._constantDefs.sort(key=lambda x: x.glname)
+
+        # Get dicts
+        for definition in self._functionDefs:
+            self._functions[definition.shortname] = definition
+        for definition in self._constantDefs:
+            self._constants[definition.shortname] = definition
+
         # Get some stats
-        for funcDef in self.functionDefs:
+        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])
+        n1 = len([d for d in self._constantDefs])
+        n2 = len([d for d in self._functionDefs])
+        n3 = len([d for d in self._functionDefs if d.group])
+        n4 = sum([len(d.group) for d in self._functionDefs if d.group])
         print('Found %i constants and %i unique functions (%i groups contain %i functions)").' % (
-               n1, n2, n3, n4))
-        
+            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])
-    
+        """ Sorted list of constant names.
+        """
+        return [d.shortname for d in self._constantDefs]
+
     @property
     def function_names(self):
-        return set([d.cname for d in self.functionDefs])
-    
+        """ Sorted list of function names.
+        """
+        return [d.shortname for d in self._functionDefs]
+
+    @property
+    def constants(self):
+        """ Dict with all the constants.
+        """
+        return self._constants
+
+    @property
+    def functions(self):
+        """ Dict witj all the functions.
+        """
+        return self._functions
+
     def show_groups(self):
-        for d in self.functionDefs:
+        for d in self._functionDefs:
             if isinstance(d.group, list):
                 print(d.keyname)
                 for d2 in d.group:
-                    print('  ', d2.cname)
+                    print('  ', d2.glname)
 
 
 
 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.oname = ''  # original name
+        self.shortname = self.glname = ''  # short and long name
         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
+        # Do initial parsing of the incoming line
+        # (which may be multiline, actually)
         pass
 
+    def _set_name(self, name):
+        # Store original name
+        self.oname = name
+        # Store plain name
+        if name.startswith('GL_'):
+            name = name[3:]
+        elif name.startswith('gl'):
+            name = name[2].lower() + name[3:]
+        self.shortname = name
+        # Store gl name
+        if name.upper() == name:
+            name = 'GL_' + name
+        else:
+            name = 'gl' + name[0].upper() + name[1:]
+        self.glname = name
+
 
 
 class ConstantDefinition(Definition):
-    
+
     def parse_line(self, line):
         """ Set cname and value attributes.
         """
         self.value = None
-        line = line.split('/*',1)[0]
+        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)
+            name, val = args
+            self.isvalid = bool(name)
+            self._set_name(name)
+            self._set_value_from_string(val)
+        elif '=' in args:
+            name, val = args[-3], args[-1]
+            self.isvalid = bool(name)
+            self._set_name(name)
+            self._set_value_from_string(val)
         else:
             print('Dont know what to do with "%s"' % line)
-        
+
+        # For when this constant is reused to set another constant
         if self.value is not None:
-            CONSTANTS[self.cname] = self.value
-    
-    def process(self):
-        pass  # We did all that we needed to do
+            CONSTANTS[self.oname] = self.value
+
+    def _set_value_from_string(self, val):
+        # Set value
+        val = val.strip(';')
+        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)
 
 
 
 class FunctionDefinition(Definition):
     
+    SKIPTYPECHARS = 'if'  # 'bsifd'
+    ALLSKIPCHARS = SKIPTYPECHARS + 'v1234'
+    
     def parse_line(self, line):
         """ Set cname, keyname, cargs attributes.
+        The list of args always has one entry and the first entry is always
+        the output (can be void).
         """
         # Parse components
         beforeBrace, args = line.split('(', 1)
         betweenBraces, _ = args.split(')', 1)
         *prefix, name = getwords(beforeBrace)
-        
+
         # Store name
-        self.cname = name
-        
+        self._set_name(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.keyname = self.glname.rstrip('v').rstrip(self.SKIPTYPECHARS).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)
+        # 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) )
+        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) )
-    
-    
+                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]):
+        if self.glname.startswith(keyname):
+            extrachars = self.glname[len(keyname):]
+            if all([(c in self.ALLSKIPCHARS) 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 FunctionGroup(FunctionDefinition):
+
+    def parse_line(self, line):
+        FunctionDefinition.parse_line(self, line)
+        self.group = []
+
 
 
 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'
+            name = 'unknown_name'
+            type = components[0]
         else:
             name = components[-1]
             type = components[-2]
+            if 'const' in type:
+                type = components[-3]  # glShaderSource has "const GLchar* const* string"
         # 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]
+        self.isptr = argAsString.count('*')  # Number of stars
+        self.ctype = type.strip('*') + '*'*self.isptr
         # 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))
-    
+    pygl = set([name for name in dir(OpenGL.GL)])
+
     # Test if all functions are in pyopengl
-    print('Not in pyopengl:', gl2.function_names.difference(pygl))
-    
+    for keyfunc in gl2._functionDefs:
+        group = keyfunc.group or [keyfunc]
+        for f in group:
+            if f.glname not in pygl:
+                print('Not in pyopengl:', f.glname)
+
     # Test if constant match with these in pyopengl
-    for d in gl2.constantDefs:
+    for d in gl2._constantDefs:
         v1 = d.value
         try:
-            v2 = getattr(OpenGL.GL, d.cname)
+            v2 = getattr(OpenGL.GL, d.glname)
         except AttributeError:
-            print(d.cname, 'is not in pyopengl')
+            print(d.glname, '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
+                print(d.glname, 'does not match: %r vs %r' % (v1, int(v2)))
diff --git a/codegen/headers/egl.h b/codegen/headers/egl.h
new file mode 100644
index 0000000..99ea342
--- /dev/null
+++ b/codegen/headers/egl.h
@@ -0,0 +1,329 @@
+/* -*- mode: c; tab-width: 8; -*- */
+/* vi: set sw=4 ts=8: */
+/* Reference version of egl.h for EGL 1.4.
+ * $Revision: 9356 $ on $Date: 2009-10-21 02:52:25 -0700 (Wed, 21 Oct 2009) $
+ */
+
+/*
+** Copyright (c) 2007-2009 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are 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 Materials.
+**
+** THE MATERIALS ARE 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
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+#ifndef __egl_h_
+#define __egl_h_
+
+/* All platform-dependent types and macro boilerplate (such as EGLAPI
+ * and EGLAPIENTRY) should go in eglplatform.h.
+ */
+#include <EGL/eglplatform.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* EGL Types */
+/* EGLint is defined in eglplatform.h */
+typedef unsigned int EGLBoolean;
+typedef unsigned int EGLenum;
+typedef void *EGLConfig;
+typedef void *EGLContext;
+typedef void *EGLDisplay;
+typedef void *EGLSurface;
+typedef void *EGLClientBuffer;
+
+/* EGL Versioning */
+#define EGL_VERSION_1_0			1
+#define EGL_VERSION_1_1			1
+#define EGL_VERSION_1_2			1
+#define EGL_VERSION_1_3			1
+#define EGL_VERSION_1_4			1
+
+/* EGL Enumerants. Bitmasks and other exceptional cases aside, most
+ * enums are assigned unique values starting at 0x3000.
+ */
+
+/* EGL aliases */
+#define EGL_FALSE			0
+#define EGL_TRUE			1
+
+/* Out-of-band handle values */
+#define EGL_DEFAULT_DISPLAY		((EGLNativeDisplayType)0)
+#define EGL_NO_CONTEXT			((EGLContext)0)
+#define EGL_NO_DISPLAY			((EGLDisplay)0)
+#define EGL_NO_SURFACE			((EGLSurface)0)
+
+/* Out-of-band attribute value */
+#define EGL_DONT_CARE			((EGLint)-1)
+
+/* Errors / GetError return values */
+#define EGL_SUCCESS			0x3000
+#define EGL_NOT_INITIALIZED		0x3001
+#define EGL_BAD_ACCESS			0x3002
+#define EGL_BAD_ALLOC			0x3003
+#define EGL_BAD_ATTRIBUTE		0x3004
+#define EGL_BAD_CONFIG			0x3005
+#define EGL_BAD_CONTEXT			0x3006
+#define EGL_BAD_CURRENT_SURFACE		0x3007
+#define EGL_BAD_DISPLAY			0x3008
+#define EGL_BAD_MATCH			0x3009
+#define EGL_BAD_NATIVE_PIXMAP		0x300A
+#define EGL_BAD_NATIVE_WINDOW		0x300B
+#define EGL_BAD_PARAMETER		0x300C
+#define EGL_BAD_SURFACE			0x300D
+#define EGL_CONTEXT_LOST		0x300E	/* EGL 1.1 - IMG_power_management */
+
+/* Reserved 0x300F-0x301F for additional errors */
+
+/* Config attributes */
+#define EGL_BUFFER_SIZE			0x3020
+#define EGL_ALPHA_SIZE			0x3021
+#define EGL_BLUE_SIZE			0x3022
+#define EGL_GREEN_SIZE			0x3023
+#define EGL_RED_SIZE			0x3024
+#define EGL_DEPTH_SIZE			0x3025
+#define EGL_STENCIL_SIZE		0x3026
+#define EGL_CONFIG_CAVEAT		0x3027
+#define EGL_CONFIG_ID			0x3028
+#define EGL_LEVEL			0x3029
+#define EGL_MAX_PBUFFER_HEIGHT		0x302A
+#define EGL_MAX_PBUFFER_PIXELS		0x302B
+#define EGL_MAX_PBUFFER_WIDTH		0x302C
+#define EGL_NATIVE_RENDERABLE		0x302D
+#define EGL_NATIVE_VISUAL_ID		0x302E
+#define EGL_NATIVE_VISUAL_TYPE		0x302F
+#define EGL_SAMPLES			0x3031
+#define EGL_SAMPLE_BUFFERS		0x3032
+#define EGL_SURFACE_TYPE		0x3033
+#define EGL_TRANSPARENT_TYPE		0x3034
+#define EGL_TRANSPARENT_BLUE_VALUE	0x3035
+#define EGL_TRANSPARENT_GREEN_VALUE	0x3036
+#define EGL_TRANSPARENT_RED_VALUE	0x3037
+#define EGL_NONE			0x3038	/* Attrib list terminator */
+#define EGL_BIND_TO_TEXTURE_RGB		0x3039
+#define EGL_BIND_TO_TEXTURE_RGBA	0x303A
+#define EGL_MIN_SWAP_INTERVAL		0x303B
+#define EGL_MAX_SWAP_INTERVAL		0x303C
+#define EGL_LUMINANCE_SIZE		0x303D
+#define EGL_ALPHA_MASK_SIZE		0x303E
+#define EGL_COLOR_BUFFER_TYPE		0x303F
+#define EGL_RENDERABLE_TYPE		0x3040
+#define EGL_MATCH_NATIVE_PIXMAP		0x3041	/* Pseudo-attribute (not queryable) */
+#define EGL_CONFORMANT			0x3042
+
+/* Reserved 0x3041-0x304F for additional config attributes */
+
+/* Config attribute values */
+#define EGL_SLOW_CONFIG			0x3050	/* EGL_CONFIG_CAVEAT value */
+#define EGL_NON_CONFORMANT_CONFIG	0x3051	/* EGL_CONFIG_CAVEAT value */
+#define EGL_TRANSPARENT_RGB		0x3052	/* EGL_TRANSPARENT_TYPE value */
+#define EGL_RGB_BUFFER			0x308E	/* EGL_COLOR_BUFFER_TYPE value */
+#define EGL_LUMINANCE_BUFFER		0x308F	/* EGL_COLOR_BUFFER_TYPE value */
+
+/* More config attribute values, for EGL_TEXTURE_FORMAT */
+#define EGL_NO_TEXTURE			0x305C
+#define EGL_TEXTURE_RGB			0x305D
+#define EGL_TEXTURE_RGBA		0x305E
+#define EGL_TEXTURE_2D			0x305F
+
+/* Config attribute mask bits */
+#define EGL_PBUFFER_BIT			0x0001	/* EGL_SURFACE_TYPE mask bits */
+#define EGL_PIXMAP_BIT			0x0002	/* EGL_SURFACE_TYPE mask bits */
+#define EGL_WINDOW_BIT			0x0004	/* EGL_SURFACE_TYPE mask bits */
+#define EGL_VG_COLORSPACE_LINEAR_BIT	0x0020	/* EGL_SURFACE_TYPE mask bits */
+#define EGL_VG_ALPHA_FORMAT_PRE_BIT	0x0040	/* EGL_SURFACE_TYPE mask bits */
+#define EGL_MULTISAMPLE_RESOLVE_BOX_BIT 0x0200	/* EGL_SURFACE_TYPE mask bits */
+#define EGL_SWAP_BEHAVIOR_PRESERVED_BIT 0x0400	/* EGL_SURFACE_TYPE mask bits */
+
+#define EGL_OPENGL_ES_BIT		0x0001	/* EGL_RENDERABLE_TYPE mask bits */
+#define EGL_OPENVG_BIT			0x0002	/* EGL_RENDERABLE_TYPE mask bits */
+#define EGL_OPENGL_ES2_BIT		0x0004	/* EGL_RENDERABLE_TYPE mask bits */
+#define EGL_OPENGL_BIT			0x0008	/* EGL_RENDERABLE_TYPE mask bits */
+
+/* QueryString targets */
+#define EGL_VENDOR			0x3053
+#define EGL_VERSION			0x3054
+#define EGL_EXTENSIONS			0x3055
+#define EGL_CLIENT_APIS			0x308D
+
+/* QuerySurface / SurfaceAttrib / CreatePbufferSurface targets */
+#define EGL_HEIGHT			0x3056
+#define EGL_WIDTH			0x3057
+#define EGL_LARGEST_PBUFFER		0x3058
+#define EGL_TEXTURE_FORMAT		0x3080
+#define EGL_TEXTURE_TARGET		0x3081
+#define EGL_MIPMAP_TEXTURE		0x3082
+#define EGL_MIPMAP_LEVEL		0x3083
+#define EGL_RENDER_BUFFER		0x3086
+#define EGL_VG_COLORSPACE		0x3087
+#define EGL_VG_ALPHA_FORMAT		0x3088
+#define EGL_HORIZONTAL_RESOLUTION	0x3090
+#define EGL_VERTICAL_RESOLUTION		0x3091
+#define EGL_PIXEL_ASPECT_RATIO		0x3092
+#define EGL_SWAP_BEHAVIOR		0x3093
+#define EGL_MULTISAMPLE_RESOLVE		0x3099
+
+/* EGL_RENDER_BUFFER values / BindTexImage / ReleaseTexImage buffer targets */
+#define EGL_BACK_BUFFER			0x3084
+#define EGL_SINGLE_BUFFER		0x3085
+
+/* OpenVG color spaces */
+#define EGL_VG_COLORSPACE_sRGB		0x3089	/* EGL_VG_COLORSPACE value */
+#define EGL_VG_COLORSPACE_LINEAR	0x308A	/* EGL_VG_COLORSPACE value */
+
+/* OpenVG alpha formats */
+#define EGL_VG_ALPHA_FORMAT_NONPRE	0x308B	/* EGL_ALPHA_FORMAT value */
+#define EGL_VG_ALPHA_FORMAT_PRE		0x308C	/* EGL_ALPHA_FORMAT value */
+
+/* Constant scale factor by which fractional display resolutions &
+ * aspect ratio are scaled when queried as integer values.
+ */
+#define EGL_DISPLAY_SCALING		10000
+
+/* Unknown display resolution/aspect ratio */
+#define EGL_UNKNOWN			((EGLint)-1)
+
+/* Back buffer swap behaviors */
+#define EGL_BUFFER_PRESERVED		0x3094	/* EGL_SWAP_BEHAVIOR value */
+#define EGL_BUFFER_DESTROYED		0x3095	/* EGL_SWAP_BEHAVIOR value */
+
+/* CreatePbufferFromClientBuffer buffer types */
+#define EGL_OPENVG_IMAGE		0x3096
+
+/* QueryContext targets */
+#define EGL_CONTEXT_CLIENT_TYPE		0x3097
+
+/* CreateContext attributes */
+#define EGL_CONTEXT_CLIENT_VERSION	0x3098
+
+/* Multisample resolution behaviors */
+#define EGL_MULTISAMPLE_RESOLVE_DEFAULT 0x309A	/* EGL_MULTISAMPLE_RESOLVE value */
+#define EGL_MULTISAMPLE_RESOLVE_BOX	0x309B	/* EGL_MULTISAMPLE_RESOLVE value */
+
+/* BindAPI/QueryAPI targets */
+#define EGL_OPENGL_ES_API		0x30A0
+#define EGL_OPENVG_API			0x30A1
+#define EGL_OPENGL_API			0x30A2
+
+/* GetCurrentSurface targets */
+#define EGL_DRAW			0x3059
+#define EGL_READ			0x305A
+
+/* WaitNative engines */
+#define EGL_CORE_NATIVE_ENGINE		0x305B
+
+/* EGL 1.2 tokens renamed for consistency in EGL 1.3 */
+#define EGL_COLORSPACE			EGL_VG_COLORSPACE
+#define EGL_ALPHA_FORMAT		EGL_VG_ALPHA_FORMAT
+#define EGL_COLORSPACE_sRGB		EGL_VG_COLORSPACE_sRGB
+#define EGL_COLORSPACE_LINEAR		EGL_VG_COLORSPACE_LINEAR
+#define EGL_ALPHA_FORMAT_NONPRE		EGL_VG_ALPHA_FORMAT_NONPRE
+#define EGL_ALPHA_FORMAT_PRE		EGL_VG_ALPHA_FORMAT_PRE
+
+/* EGL extensions must request enum blocks from the Khronos
+ * API Registrar, who maintains the enumerant registry. Submit
+ * a bug in Khronos Bugzilla against task "Registry".
+ */
+
+
+
+/* EGL Functions */
+
+EGLAPI EGLint EGLAPIENTRY eglGetError(void);
+
+EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display_id);
+EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
+EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy);
+
+EGLAPI const char * EGLAPIENTRY eglQueryString(EGLDisplay dpy, EGLint name);
+
+EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs(EGLDisplay dpy, EGLConfig *configs,
+			 EGLint config_size, EGLint *num_config);
+EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list,
+			   EGLConfig *configs, EGLint config_size,
+			   EGLint *num_config);
+EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config,
+			      EGLint attribute, EGLint *value);
+
+EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,
+				  EGLNativeWindowType win,
+				  const EGLint *attrib_list);
+EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config,
+				   const EGLint *attrib_list);
+EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config,
+				  EGLNativePixmapType pixmap,
+				  const EGLint *attrib_list);
+EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface(EGLDisplay dpy, EGLSurface surface);
+EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface(EGLDisplay dpy, EGLSurface surface,
+			   EGLint attribute, EGLint *value);
+
+EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI(EGLenum api);
+EGLAPI EGLenum EGLAPIENTRY eglQueryAPI(void);
+
+EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient(void);
+
+EGLAPI EGLBoolean EGLAPIENTRY eglReleaseThread(void);
+
+EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferFromClientBuffer(
+	      EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer,
+	      EGLConfig config, const EGLint *attrib_list);
+
+EGLAPI EGLBoolean EGLAPIENTRY eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface,
+			    EGLint attribute, EGLint value);
+EGLAPI EGLBoolean EGLAPIENTRY eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+EGLAPI EGLBoolean EGLAPIENTRY eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+
+
+EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval);
+
+
+EGLAPI EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay dpy, EGLConfig config,
+			    EGLContext share_context,
+			    const EGLint *attrib_list);
+EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay dpy, EGLContext ctx);
+EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay dpy, EGLSurface draw,
+			  EGLSurface read, EGLContext ctx);
+
+EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext(void);
+EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface(EGLint readdraw);
+EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void);
+EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay dpy, EGLContext ctx,
+			   EGLint attribute, EGLint *value);
+
+EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL(void);
+EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative(EGLint engine);
+EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers(EGLDisplay dpy, EGLSurface surface);
+EGLAPI EGLBoolean EGLAPIENTRY eglCopyBuffers(EGLDisplay dpy, EGLSurface surface,
+			  EGLNativePixmapType target);
+
+/* This is a generic function pointer type, whose name indicates it must
+ * be cast to the proper type *and calling convention* before use.
+ */
+typedef void (*__eglMustCastToProperFunctionPointerType)(void);
+
+/* Now, define eglGetProcAddress using the generic function ptr. type */
+EGLAPI __eglMustCastToProperFunctionPointerType EGLAPIENTRY
+       eglGetProcAddress(const char *procname);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __egl_h_ */
diff --git a/codegen/headers/webgl.idl b/codegen/headers/webgl.idl
new file mode 100644
index 0000000..dee0a14
--- /dev/null
+++ b/codegen/headers/webgl.idl
@@ -0,0 +1,743 @@
+// AUTOGENERATED FILE -- DO NOT EDIT -- SEE Makefile
+//
+// WebGL IDL definitions scraped from the Khronos specification:
+// https://www.khronos.org/registry/webgl/specs/latest/
+//
+// This IDL depends on the typed array specification defined at:
+// https://www.khronos.org/registry/typedarray/specs/latest/typedarrays.idl
+
+typedef unsigned long  GLenum;
+typedef boolean        GLboolean;
+typedef unsigned long  GLbitfield;
+typedef byte           GLbyte;         /* 'byte' should be a signed 8 bit type. */
+typedef short          GLshort;
+typedef long           GLint;
+typedef long           GLsizei;
+typedef long long      GLintptr;
+typedef long long      GLsizeiptr;
+// Ideally the typedef below would use 'unsigned byte', but that doesn't currently exist in Web IDL.
+typedef octet          GLubyte;        /* 'octet' should be an unsigned 8 bit type. */
+typedef unsigned short GLushort;
+typedef unsigned long  GLuint;
+typedef unrestricted float GLfloat;
+typedef unrestricted float GLclampf;  
+
+
+dictionary WebGLContextAttributes {
+    GLboolean alpha = true;
+    GLboolean depth = true;
+    GLboolean stencil = false;
+    GLboolean antialias = true;
+    GLboolean premultipliedAlpha = true;
+    GLboolean preserveDrawingBuffer = false;
+    GLboolean preferLowPowerToHighPerformance = false;
+    GLboolean failIfMajorPerformanceCaveat = false;
+};
+
+interface WebGLObject {
+};
+
+interface WebGLBuffer : WebGLObject {
+};
+
+interface WebGLFramebuffer : WebGLObject {
+};
+
+interface WebGLProgram : WebGLObject {
+};
+
+interface WebGLRenderbuffer : WebGLObject {
+};
+
+interface WebGLShader : WebGLObject {
+};
+
+interface WebGLTexture : WebGLObject {
+};
+
+interface WebGLUniformLocation {
+};
+
+interface WebGLActiveInfo {
+    readonly attribute GLint size;
+    readonly attribute GLenum type;
+    readonly attribute DOMString name;
+};
+
+interface WebGLShaderPrecisionFormat {
+    readonly attribute GLint rangeMin;
+    readonly attribute GLint rangeMax;
+    readonly attribute GLint precision;
+};
+
+[NoInterfaceObject]
+interface WebGLRenderingContextBase
+{
+
+    /* ClearBufferMask */
+    const GLenum DEPTH_BUFFER_BIT               = 0x00000100;
+    const GLenum STENCIL_BUFFER_BIT             = 0x00000400;
+    const GLenum COLOR_BUFFER_BIT               = 0x00004000;
+    
+    /* BeginMode */
+    const GLenum POINTS                         = 0x0000;
+    const GLenum LINES                          = 0x0001;
+    const GLenum LINE_LOOP                      = 0x0002;
+    const GLenum LINE_STRIP                     = 0x0003;
+    const GLenum TRIANGLES                      = 0x0004;
+    const GLenum TRIANGLE_STRIP                 = 0x0005;
+    const GLenum TRIANGLE_FAN                   = 0x0006;
+    
+    /* AlphaFunction (not supported in ES20) */
+    /*      NEVER */
+    /*      LESS */
+    /*      EQUAL */
+    /*      LEQUAL */
+    /*      GREATER */
+    /*      NOTEQUAL */
+    /*      GEQUAL */
+    /*      ALWAYS */
+    
+    /* BlendingFactorDest */
+    const GLenum ZERO                           = 0;
+    const GLenum ONE                            = 1;
+    const GLenum SRC_COLOR                      = 0x0300;
+    const GLenum ONE_MINUS_SRC_COLOR            = 0x0301;
+    const GLenum SRC_ALPHA                      = 0x0302;
+    const GLenum ONE_MINUS_SRC_ALPHA            = 0x0303;
+    const GLenum DST_ALPHA                      = 0x0304;
+    const GLenum ONE_MINUS_DST_ALPHA            = 0x0305;
+    
+    /* BlendingFactorSrc */
+    /*      ZERO */
+    /*      ONE */
+    const GLenum DST_COLOR                      = 0x0306;
+    const GLenum ONE_MINUS_DST_COLOR            = 0x0307;
+    const GLenum SRC_ALPHA_SATURATE             = 0x0308;
+    /*      SRC_ALPHA */
+    /*      ONE_MINUS_SRC_ALPHA */
+    /*      DST_ALPHA */
+    /*      ONE_MINUS_DST_ALPHA */
+    
+    /* BlendEquationSeparate */
+    const GLenum FUNC_ADD                       = 0x8006;
+    const GLenum BLEND_EQUATION                 = 0x8009;
+    const GLenum BLEND_EQUATION_RGB             = 0x8009;   /* same as BLEND_EQUATION */
+    const GLenum BLEND_EQUATION_ALPHA           = 0x883D;
+    
+    /* BlendSubtract */
+    const GLenum FUNC_SUBTRACT                  = 0x800A;
+    const GLenum FUNC_REVERSE_SUBTRACT          = 0x800B;
+    
+    /* Separate Blend Functions */
+    const GLenum BLEND_DST_RGB                  = 0x80C8;
+    const GLenum BLEND_SRC_RGB                  = 0x80C9;
+    const GLenum BLEND_DST_ALPHA                = 0x80CA;
+    const GLenum BLEND_SRC_ALPHA                = 0x80CB;
+    const GLenum CONSTANT_COLOR                 = 0x8001;
+    const GLenum ONE_MINUS_CONSTANT_COLOR       = 0x8002;
+    const GLenum CONSTANT_ALPHA                 = 0x8003;
+    const GLenum ONE_MINUS_CONSTANT_ALPHA       = 0x8004;
+    const GLenum BLEND_COLOR                    = 0x8005;
+    
+    /* Buffer Objects */
+    const GLenum ARRAY_BUFFER                   = 0x8892;
+    const GLenum ELEMENT_ARRAY_BUFFER           = 0x8893;
+    const GLenum ARRAY_BUFFER_BINDING           = 0x8894;
+    const GLenum ELEMENT_ARRAY_BUFFER_BINDING   = 0x8895;
+    
+    const GLenum STREAM_DRAW                    = 0x88E0;
+    const GLenum STATIC_DRAW                    = 0x88E4;
+    const GLenum DYNAMIC_DRAW                   = 0x88E8;
+    
+    const GLenum BUFFER_SIZE                    = 0x8764;
+    const GLenum BUFFER_USAGE                   = 0x8765;
+    
+    const GLenum CURRENT_VERTEX_ATTRIB          = 0x8626;
+    
+    /* CullFaceMode */
+    const GLenum FRONT                          = 0x0404;
+    const GLenum BACK                           = 0x0405;
+    const GLenum FRONT_AND_BACK                 = 0x0408;
+    
+    /* DepthFunction */
+    /*      NEVER */
+    /*      LESS */
+    /*      EQUAL */
+    /*      LEQUAL */
+    /*      GREATER */
+    /*      NOTEQUAL */
+    /*      GEQUAL */
+    /*      ALWAYS */
+    
+    /* EnableCap */
+    /* TEXTURE_2D */
+    const GLenum CULL_FACE                      = 0x0B44;
+    const GLenum BLEND                          = 0x0BE2;
+    const GLenum DITHER                         = 0x0BD0;
+    const GLenum STENCIL_TEST                   = 0x0B90;
+    const GLenum DEPTH_TEST                     = 0x0B71;
+    const GLenum SCISSOR_TEST                   = 0x0C11;
+    const GLenum POLYGON_OFFSET_FILL            = 0x8037;
+    const GLenum SAMPLE_ALPHA_TO_COVERAGE       = 0x809E;
+    const GLenum SAMPLE_COVERAGE                = 0x80A0;
+    
+    /* ErrorCode */
+    const GLenum NO_ERROR                       = 0;
+    const GLenum INVALID_ENUM                   = 0x0500;
+    const GLenum INVALID_VALUE                  = 0x0501;
+    const GLenum INVALID_OPERATION              = 0x0502;
+    const GLenum OUT_OF_MEMORY                  = 0x0505;
+    
+    /* FrontFaceDirection */
+    const GLenum CW                             = 0x0900;
+    const GLenum CCW                            = 0x0901;
+    
+    /* GetPName */
+    const GLenum LINE_WIDTH                     = 0x0B21;
+    const GLenum ALIASED_POINT_SIZE_RANGE       = 0x846D;
+    const GLenum ALIASED_LINE_WIDTH_RANGE       = 0x846E;
+    const GLenum CULL_FACE_MODE                 = 0x0B45;
+    const GLenum FRONT_FACE                     = 0x0B46;
+    const GLenum DEPTH_RANGE                    = 0x0B70;
+    const GLenum DEPTH_WRITEMASK                = 0x0B72;
+    const GLenum DEPTH_CLEAR_VALUE              = 0x0B73;
+    const GLenum DEPTH_FUNC                     = 0x0B74;
+    const GLenum STENCIL_CLEAR_VALUE            = 0x0B91;
+    const GLenum STENCIL_FUNC                   = 0x0B92;
+    const GLenum STENCIL_FAIL                   = 0x0B94;
+    const GLenum STENCIL_PASS_DEPTH_FAIL        = 0x0B95;
+    const GLenum STENCIL_PASS_DEPTH_PASS        = 0x0B96;
+    const GLenum STENCIL_REF                    = 0x0B97;
+    const GLenum STENCIL_VALUE_MASK             = 0x0B93;
+    const GLenum STENCIL_WRITEMASK              = 0x0B98;
+    const GLenum STENCIL_BACK_FUNC              = 0x8800;
+    const GLenum STENCIL_BACK_FAIL              = 0x8801;
+    const GLenum STENCIL_BACK_PASS_DEPTH_FAIL   = 0x8802;
+    const GLenum STENCIL_BACK_PASS_DEPTH_PASS   = 0x8803;
+    const GLenum STENCIL_BACK_REF               = 0x8CA3;
+    const GLenum STENCIL_BACK_VALUE_MASK        = 0x8CA4;
+    const GLenum STENCIL_BACK_WRITEMASK         = 0x8CA5;
+    const GLenum VIEWPORT                       = 0x0BA2;
+    const GLenum SCISSOR_BOX                    = 0x0C10;
+    /*      SCISSOR_TEST */
+    const GLenum COLOR_CLEAR_VALUE              = 0x0C22;
+    const GLenum COLOR_WRITEMASK                = 0x0C23;
+    const GLenum UNPACK_ALIGNMENT               = 0x0CF5;
+    const GLenum PACK_ALIGNMENT                 = 0x0D05;
+    const GLenum MAX_TEXTURE_SIZE               = 0x0D33;
+    const GLenum MAX_VIEWPORT_DIMS              = 0x0D3A;
+    const GLenum SUBPIXEL_BITS                  = 0x0D50;
+    const GLenum RED_BITS                       = 0x0D52;
+    const GLenum GREEN_BITS                     = 0x0D53;
+    const GLenum BLUE_BITS                      = 0x0D54;
+    const GLenum ALPHA_BITS                     = 0x0D55;
+    const GLenum DEPTH_BITS                     = 0x0D56;
+    const GLenum STENCIL_BITS                   = 0x0D57;
+    const GLenum POLYGON_OFFSET_UNITS           = 0x2A00;
+    /*      POLYGON_OFFSET_FILL */
+    const GLenum POLYGON_OFFSET_FACTOR          = 0x8038;
+    const GLenum TEXTURE_BINDING_2D             = 0x8069;
+    const GLenum SAMPLE_BUFFERS                 = 0x80A8;
+    const GLenum SAMPLES                        = 0x80A9;
+    const GLenum SAMPLE_COVERAGE_VALUE          = 0x80AA;
+    const GLenum SAMPLE_COVERAGE_INVERT         = 0x80AB;
+    
+    /* GetTextureParameter */
+    /*      TEXTURE_MAG_FILTER */
+    /*      TEXTURE_MIN_FILTER */
+    /*      TEXTURE_WRAP_S */
+    /*      TEXTURE_WRAP_T */
+    
+    const GLenum COMPRESSED_TEXTURE_FORMATS     = 0x86A3;
+    
+    /* HintMode */
+    const GLenum DONT_CARE                      = 0x1100;
+    const GLenum FASTEST                        = 0x1101;
+    const GLenum NICEST                         = 0x1102;
+    
+    /* HintTarget */
+    const GLenum GENERATE_MIPMAP_HINT            = 0x8192;
+    
+    /* DataType */
+    const GLenum BYTE                           = 0x1400;
+    const GLenum UNSIGNED_BYTE                  = 0x1401;
+    const GLenum SHORT                          = 0x1402;
+    const GLenum UNSIGNED_SHORT                 = 0x1403;
+    const GLenum INT                            = 0x1404;
+    const GLenum UNSIGNED_INT                   = 0x1405;
+    const GLenum FLOAT                          = 0x1406;
+    
+    /* PixelFormat */
+    const GLenum DEPTH_COMPONENT                = 0x1902;
+    const GLenum ALPHA                          = 0x1906;
+    const GLenum RGB                            = 0x1907;
+    const GLenum RGBA                           = 0x1908;
+    const GLenum LUMINANCE                      = 0x1909;
+    const GLenum LUMINANCE_ALPHA                = 0x190A;
+    
+    /* PixelType */
+    /*      UNSIGNED_BYTE */
+    const GLenum UNSIGNED_SHORT_4_4_4_4         = 0x8033;
+    const GLenum UNSIGNED_SHORT_5_5_5_1         = 0x8034;
+    const GLenum UNSIGNED_SHORT_5_6_5           = 0x8363;
+    
+    /* Shaders */
+    const GLenum FRAGMENT_SHADER                  = 0x8B30;
+    const GLenum VERTEX_SHADER                    = 0x8B31;
+    const GLenum MAX_VERTEX_ATTRIBS               = 0x8869;
+    const GLenum MAX_VERTEX_UNIFORM_VECTORS       = 0x8DFB;
+    const GLenum MAX_VARYING_VECTORS              = 0x8DFC;
+    const GLenum MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D;
+    const GLenum MAX_VERTEX_TEXTURE_IMAGE_UNITS   = 0x8B4C;
+    const GLenum MAX_TEXTURE_IMAGE_UNITS          = 0x8872;
+    const GLenum MAX_FRAGMENT_UNIFORM_VECTORS     = 0x8DFD;
+    const GLenum SHADER_TYPE                      = 0x8B4F;
+    const GLenum DELETE_STATUS                    = 0x8B80;
+    const GLenum LINK_STATUS                      = 0x8B82;
+    const GLenum VALIDATE_STATUS                  = 0x8B83;
+    const GLenum ATTACHED_SHADERS                 = 0x8B85;
+    const GLenum ACTIVE_UNIFORMS                  = 0x8B86;
+    const GLenum ACTIVE_ATTRIBUTES                = 0x8B89;
+    const GLenum SHADING_LANGUAGE_VERSION         = 0x8B8C;
+    const GLenum CURRENT_PROGRAM                  = 0x8B8D;
+    
+    /* StencilFunction */
+    const GLenum NEVER                          = 0x0200;
+    const GLenum LESS                           = 0x0201;
+    const GLenum EQUAL                          = 0x0202;
+    const GLenum LEQUAL                         = 0x0203;
+    const GLenum GREATER                        = 0x0204;
+    const GLenum NOTEQUAL                       = 0x0205;
+    const GLenum GEQUAL                         = 0x0206;
+    const GLenum ALWAYS                         = 0x0207;
+    
+    /* StencilOp */
+    /*      ZERO */
+    const GLenum KEEP                           = 0x1E00;
+    const GLenum REPLACE                        = 0x1E01;
+    const GLenum INCR                           = 0x1E02;
+    const GLenum DECR                           = 0x1E03;
+    const GLenum INVERT                         = 0x150A;
+    const GLenum INCR_WRAP                      = 0x8507;
+    const GLenum DECR_WRAP                      = 0x8508;
+    
+    /* StringName */
+    const GLenum VENDOR                         = 0x1F00;
+    const GLenum RENDERER                       = 0x1F01;
+    const GLenum VERSION                        = 0x1F02;
+    
+    /* TextureMagFilter */
+    const GLenum NEAREST                        = 0x2600;
+    const GLenum LINEAR                         = 0x2601;
+    
+    /* TextureMinFilter */
+    /*      NEAREST */
+    /*      LINEAR */
+    const GLenum NEAREST_MIPMAP_NEAREST         = 0x2700;
+    const GLenum LINEAR_MIPMAP_NEAREST          = 0x2701;
+    const GLenum NEAREST_MIPMAP_LINEAR          = 0x2702;
+    const GLenum LINEAR_MIPMAP_LINEAR           = 0x2703;
+    
+    /* TextureParameterName */
+    const GLenum TEXTURE_MAG_FILTER             = 0x2800;
+    const GLenum TEXTURE_MIN_FILTER             = 0x2801;
+    const GLenum TEXTURE_WRAP_S                 = 0x2802;
+    const GLenum TEXTURE_WRAP_T                 = 0x2803;
+    
+    /* TextureTarget */
+    const GLenum TEXTURE_2D                     = 0x0DE1;
+    const GLenum TEXTURE                        = 0x1702;
+    
+    const GLenum TEXTURE_CUBE_MAP               = 0x8513;
+    const GLenum TEXTURE_BINDING_CUBE_MAP       = 0x8514;
+    const GLenum TEXTURE_CUBE_MAP_POSITIVE_X    = 0x8515;
+    const GLenum TEXTURE_CUBE_MAP_NEGATIVE_X    = 0x8516;
+    const GLenum TEXTURE_CUBE_MAP_POSITIVE_Y    = 0x8517;
+    const GLenum TEXTURE_CUBE_MAP_NEGATIVE_Y    = 0x8518;
+    const GLenum TEXTURE_CUBE_MAP_POSITIVE_Z    = 0x8519;
+    const GLenum TEXTURE_CUBE_MAP_NEGATIVE_Z    = 0x851A;
+    const GLenum MAX_CUBE_MAP_TEXTURE_SIZE      = 0x851C;
+    
+    /* TextureUnit */
+    const GLenum TEXTURE0                       = 0x84C0;
+    const GLenum TEXTURE1                       = 0x84C1;
+    const GLenum TEXTURE2                       = 0x84C2;
+    const GLenum TEXTURE3                       = 0x84C3;
+    const GLenum TEXTURE4                       = 0x84C4;
+    const GLenum TEXTURE5                       = 0x84C5;
+    const GLenum TEXTURE6                       = 0x84C6;
+    const GLenum TEXTURE7                       = 0x84C7;
+    const GLenum TEXTURE8                       = 0x84C8;
+    const GLenum TEXTURE9                       = 0x84C9;
+    const GLenum TEXTURE10                      = 0x84CA;
+    const GLenum TEXTURE11                      = 0x84CB;
+    const GLenum TEXTURE12                      = 0x84CC;
+    const GLenum TEXTURE13                      = 0x84CD;
+    const GLenum TEXTURE14                      = 0x84CE;
+    const GLenum TEXTURE15                      = 0x84CF;
+    const GLenum TEXTURE16                      = 0x84D0;
+    const GLenum TEXTURE17                      = 0x84D1;
+    const GLenum TEXTURE18                      = 0x84D2;
+    const GLenum TEXTURE19                      = 0x84D3;
+    const GLenum TEXTURE20                      = 0x84D4;
+    const GLenum TEXTURE21                      = 0x84D5;
+    const GLenum TEXTURE22                      = 0x84D6;
+    const GLenum TEXTURE23                      = 0x84D7;
+    const GLenum TEXTURE24                      = 0x84D8;
+    const GLenum TEXTURE25                      = 0x84D9;
+    const GLenum TEXTURE26                      = 0x84DA;
+    const GLenum TEXTURE27                      = 0x84DB;
+    const GLenum TEXTURE28                      = 0x84DC;
+    const GLenum TEXTURE29                      = 0x84DD;
+    const GLenum TEXTURE30                      = 0x84DE;
+    const GLenum TEXTURE31                      = 0x84DF;
+    const GLenum ACTIVE_TEXTURE                 = 0x84E0;
+    
+    /* TextureWrapMode */
+    const GLenum REPEAT                         = 0x2901;
+    const GLenum CLAMP_TO_EDGE                  = 0x812F;
+    const GLenum MIRRORED_REPEAT                = 0x8370;
+    
+    /* Uniform Types */
+    const GLenum FLOAT_VEC2                     = 0x8B50;
+    const GLenum FLOAT_VEC3                     = 0x8B51;
+    const GLenum FLOAT_VEC4                     = 0x8B52;
+    const GLenum INT_VEC2                       = 0x8B53;
+    const GLenum INT_VEC3                       = 0x8B54;
+    const GLenum INT_VEC4                       = 0x8B55;
+    const GLenum BOOL                           = 0x8B56;
+    const GLenum BOOL_VEC2                      = 0x8B57;
+    const GLenum BOOL_VEC3                      = 0x8B58;
+    const GLenum BOOL_VEC4                      = 0x8B59;
+    const GLenum FLOAT_MAT2                     = 0x8B5A;
+    const GLenum FLOAT_MAT3                     = 0x8B5B;
+    const GLenum FLOAT_MAT4                     = 0x8B5C;
+    const GLenum SAMPLER_2D                     = 0x8B5E;
+    const GLenum SAMPLER_CUBE                   = 0x8B60;
+    
+    /* Vertex Arrays */
+    const GLenum VERTEX_ATTRIB_ARRAY_ENABLED        = 0x8622;
+    const GLenum VERTEX_ATTRIB_ARRAY_SIZE           = 0x8623;
+    const GLenum VERTEX_ATTRIB_ARRAY_STRIDE         = 0x8624;
+    const GLenum VERTEX_ATTRIB_ARRAY_TYPE           = 0x8625;
+    const GLenum VERTEX_ATTRIB_ARRAY_NORMALIZED     = 0x886A;
+    const GLenum VERTEX_ATTRIB_ARRAY_POINTER        = 0x8645;
+    const GLenum VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F;
+    
+    /* Shader Source */
+    const GLenum COMPILE_STATUS                 = 0x8B81;
+    
+    /* Shader Precision-Specified Types */
+    const GLenum LOW_FLOAT                      = 0x8DF0;
+    const GLenum MEDIUM_FLOAT                   = 0x8DF1;
+    const GLenum HIGH_FLOAT                     = 0x8DF2;
+    const GLenum LOW_INT                        = 0x8DF3;
+    const GLenum MEDIUM_INT                     = 0x8DF4;
+    const GLenum HIGH_INT                       = 0x8DF5;
+    
+    /* Framebuffer Object. */
+    const GLenum FRAMEBUFFER                    = 0x8D40;
+    const GLenum RENDERBUFFER                   = 0x8D41;
+    
+    const GLenum RGBA4                          = 0x8056;
+    const GLenum RGB5_A1                        = 0x8057;
+    const GLenum RGB565                         = 0x8D62;
+    const GLenum DEPTH_COMPONENT16              = 0x81A5;
+    const GLenum STENCIL_INDEX                  = 0x1901;
+    const GLenum STENCIL_INDEX8                 = 0x8D48;
+    const GLenum DEPTH_STENCIL                  = 0x84F9;
+    
+    const GLenum RENDERBUFFER_WIDTH             = 0x8D42;
+    const GLenum RENDERBUFFER_HEIGHT            = 0x8D43;
+    const GLenum RENDERBUFFER_INTERNAL_FORMAT   = 0x8D44;
+    const GLenum RENDERBUFFER_RED_SIZE          = 0x8D50;
+    const GLenum RENDERBUFFER_GREEN_SIZE        = 0x8D51;
+    const GLenum RENDERBUFFER_BLUE_SIZE         = 0x8D52;
+    const GLenum RENDERBUFFER_ALPHA_SIZE        = 0x8D53;
+    const GLenum RENDERBUFFER_DEPTH_SIZE        = 0x8D54;
+    const GLenum RENDERBUFFER_STENCIL_SIZE      = 0x8D55;
+    
+    const GLenum FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE           = 0x8CD0;
+    const GLenum FRAMEBUFFER_ATTACHMENT_OBJECT_NAME           = 0x8CD1;
+    const GLenum FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL         = 0x8CD2;
+    const GLenum FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3;
+    
+    const GLenum COLOR_ATTACHMENT0              = 0x8CE0;
+    const GLenum DEPTH_ATTACHMENT               = 0x8D00;
+    const GLenum STENCIL_ATTACHMENT             = 0x8D20;
+    const GLenum DEPTH_STENCIL_ATTACHMENT       = 0x821A;
+    
+    const GLenum NONE                           = 0;
+    
+    const GLenum FRAMEBUFFER_COMPLETE                      = 0x8CD5;
+    const GLenum FRAMEBUFFER_INCOMPLETE_ATTACHMENT         = 0x8CD6;
+    const GLenum FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7;
+    const GLenum FRAMEBUFFER_INCOMPLETE_DIMENSIONS         = 0x8CD9;
+    const GLenum FRAMEBUFFER_UNSUPPORTED                   = 0x8CDD;
+    
+    const GLenum FRAMEBUFFER_BINDING            = 0x8CA6;
+    const GLenum RENDERBUFFER_BINDING           = 0x8CA7;
+    const GLenum MAX_RENDERBUFFER_SIZE          = 0x84E8;
+    
+    const GLenum INVALID_FRAMEBUFFER_OPERATION  = 0x0506;
+    
+    /* WebGL-specific enums */
+    const GLenum UNPACK_FLIP_Y_WEBGL            = 0x9240;
+    const GLenum UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
+    const GLenum CONTEXT_LOST_WEBGL             = 0x9242;
+    const GLenum UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243;
+    const GLenum BROWSER_DEFAULT_WEBGL          = 0x9244;
+
+    readonly attribute HTMLCanvasElement canvas;
+    readonly attribute GLsizei drawingBufferWidth;
+    readonly attribute GLsizei drawingBufferHeight;
+
+    [WebGLHandlesContextLoss] WebGLContextAttributes? getContextAttributes();
+    [WebGLHandlesContextLoss] boolean isContextLost();
+    
+    sequence<DOMString>? getSupportedExtensions();
+    object? getExtension(DOMString name);
+
+    void activeTexture(GLenum texture);
+    void attachShader(WebGLProgram? program, WebGLShader? shader);
+    void bindAttribLocation(WebGLProgram? program, GLuint index, DOMString name);
+    void bindBuffer(GLenum target, WebGLBuffer? buffer);
+    void bindFramebuffer(GLenum target, WebGLFramebuffer? framebuffer);
+    void bindRenderbuffer(GLenum target, WebGLRenderbuffer? renderbuffer);
+    void bindTexture(GLenum target, WebGLTexture? texture);
+    void blendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
+    void blendEquation(GLenum mode);
+    void blendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);
+    void blendFunc(GLenum sfactor, GLenum dfactor);
+    void blendFuncSeparate(GLenum srcRGB, GLenum dstRGB, 
+                           GLenum srcAlpha, GLenum dstAlpha);
+
+    void bufferData(GLenum target, GLsizeiptr size, GLenum usage);
+    void bufferData(GLenum target, ArrayBufferView data, GLenum usage);
+    void bufferData(GLenum target, ArrayBuffer? data, GLenum usage);
+    void bufferSubData(GLenum target, GLintptr offset, ArrayBufferView data);
+    void bufferSubData(GLenum target, GLintptr offset, ArrayBuffer? data);
+
+    [WebGLHandlesContextLoss] GLenum checkFramebufferStatus(GLenum target);
+    void clear(GLbitfield mask);
+    void clearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
+    void clearDepth(GLclampf depth);
+    void clearStencil(GLint s);
+    void colorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
+    void compileShader(WebGLShader? shader);
+
+    void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat,
+                              GLsizei width, GLsizei height, GLint border,
+                              ArrayBufferView data);
+    void compressedTexSubImage2D(GLenum target, GLint level,
+                                 GLint xoffset, GLint yoffset,
+                                 GLsizei width, GLsizei height, GLenum format,
+                                 ArrayBufferView data);
+
+    void copyTexImage2D(GLenum target, GLint level, GLenum internalformat, 
+                        GLint x, GLint y, GLsizei width, GLsizei height, 
+                        GLint border);
+    void copyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 
+                           GLint x, GLint y, GLsizei width, GLsizei height);
+
+    WebGLBuffer? createBuffer();
+    WebGLFramebuffer? createFramebuffer();
+    WebGLProgram? createProgram();
+    WebGLRenderbuffer? createRenderbuffer();
+    WebGLShader? createShader(GLenum type);
+    WebGLTexture? createTexture();
+
+    void cullFace(GLenum mode);
+
+    void deleteBuffer(WebGLBuffer? buffer);
+    void deleteFramebuffer(WebGLFramebuffer? framebuffer);
+    void deleteProgram(WebGLProgram? program);
+    void deleteRenderbuffer(WebGLRenderbuffer? renderbuffer);
+    void deleteShader(WebGLShader? shader);
+    void deleteTexture(WebGLTexture? texture);
+
+    void depthFunc(GLenum func);
+    void depthMask(GLboolean flag);
+    void depthRange(GLclampf zNear, GLclampf zFar);
+    void detachShader(WebGLProgram? program, WebGLShader? shader);
+    void disable(GLenum cap);
+    void disableVertexAttribArray(GLuint index);
+    void drawArrays(GLenum mode, GLint first, GLsizei count);
+    void drawElements(GLenum mode, GLsizei count, GLenum type, GLintptr offset);
+
+    void enable(GLenum cap);
+    void enableVertexAttribArray(GLuint index);
+    void finish();
+    void flush();
+    void framebufferRenderbuffer(GLenum target, GLenum attachment, 
+                                 GLenum renderbuffertarget, 
+                                 WebGLRenderbuffer? renderbuffer);
+    void framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, 
+                              WebGLTexture? texture, GLint level);
+    void frontFace(GLenum mode);
+
+    void generateMipmap(GLenum target);
+
+    WebGLActiveInfo? getActiveAttrib(WebGLProgram? program, GLuint index);
+    WebGLActiveInfo? getActiveUniform(WebGLProgram? program, GLuint index);
+    sequence<WebGLShader>? getAttachedShaders(WebGLProgram? program);
+
+    [WebGLHandlesContextLoss] GLint getAttribLocation(WebGLProgram? program, DOMString name);
+
+    any getBufferParameter(GLenum target, GLenum pname);
+    any getParameter(GLenum pname);
+
+    [WebGLHandlesContextLoss] GLenum getError();
+
+    any getFramebufferAttachmentParameter(GLenum target, GLenum attachment, 
+                                          GLenum pname);
+    any getProgramParameter(WebGLProgram? program, GLenum pname);
+    DOMString? getProgramInfoLog(WebGLProgram? program);
+    any getRenderbufferParameter(GLenum target, GLenum pname);
+    any getShaderParameter(WebGLShader? shader, GLenum pname);
+    WebGLShaderPrecisionFormat? getShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype);
+    DOMString? getShaderInfoLog(WebGLShader? shader);
+
+    DOMString? getShaderSource(WebGLShader? shader);
+
+    any getTexParameter(GLenum target, GLenum pname);
+
+    any getUniform(WebGLProgram? program, WebGLUniformLocation? location);
+
+    WebGLUniformLocation? getUniformLocation(WebGLProgram? program, DOMString name);
+
+    any getVertexAttrib(GLuint index, GLenum pname);
+
+    [WebGLHandlesContextLoss] GLsizeiptr getVertexAttribOffset(GLuint index, GLenum pname);
+
+    void hint(GLenum target, GLenum mode);
+    [WebGLHandlesContextLoss] GLboolean isBuffer(WebGLBuffer? buffer);
+    [WebGLHandlesContextLoss] GLboolean isEnabled(GLenum cap);
+    [WebGLHandlesContextLoss] GLboolean isFramebuffer(WebGLFramebuffer? framebuffer);
+    [WebGLHandlesContextLoss] GLboolean isProgram(WebGLProgram? program);
+    [WebGLHandlesContextLoss] GLboolean isRenderbuffer(WebGLRenderbuffer? renderbuffer);
+    [WebGLHandlesContextLoss] GLboolean isShader(WebGLShader? shader);
+    [WebGLHandlesContextLoss] GLboolean isTexture(WebGLTexture? texture);
+    void lineWidth(GLfloat width);
+    void linkProgram(WebGLProgram? program);
+    void pixelStorei(GLenum pname, GLint param);
+    void polygonOffset(GLfloat factor, GLfloat units);
+
+    void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, 
+                    GLenum format, GLenum type, ArrayBufferView? pixels);
+
+    void renderbufferStorage(GLenum target, GLenum internalformat, 
+                             GLsizei width, GLsizei height);
+    void sampleCoverage(GLclampf value, GLboolean invert);
+    void scissor(GLint x, GLint y, GLsizei width, GLsizei height);
+
+    void shaderSource(WebGLShader? shader, DOMString source);
+
+    void stencilFunc(GLenum func, GLint ref, GLuint mask);
+    void stencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);
+    void stencilMask(GLuint mask);
+    void stencilMaskSeparate(GLenum face, GLuint mask);
+    void stencilOp(GLenum fail, GLenum zfail, GLenum zpass);
+    void stencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
+
+    void texImage2D(GLenum target, GLint level, GLenum internalformat, 
+                    GLsizei width, GLsizei height, GLint border, GLenum format, 
+                    GLenum type, ArrayBufferView? pixels);
+    void texImage2D(GLenum target, GLint level, GLenum internalformat,
+                    GLenum format, GLenum type, ImageData? pixels);
+    void texImage2D(GLenum target, GLint level, GLenum internalformat,
+                    GLenum format, GLenum type, HTMLImageElement image); // May throw DOMException
+    void texImage2D(GLenum target, GLint level, GLenum internalformat,
+                    GLenum format, GLenum type, HTMLCanvasElement canvas); // May throw DOMException
+    void texImage2D(GLenum target, GLint level, GLenum internalformat,
+                    GLenum format, GLenum type, HTMLVideoElement video); // May throw DOMException
+
+    void texParameterf(GLenum target, GLenum pname, GLfloat param);
+    void texParameteri(GLenum target, GLenum pname, GLint param);
+
+    void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 
+                       GLsizei width, GLsizei height, 
+                       GLenum format, GLenum type, ArrayBufferView? pixels);
+    void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 
+                       GLenum format, GLenum type, ImageData? pixels);
+    void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 
+                       GLenum format, GLenum type, HTMLImageElement image); // May throw DOMException
+    void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 
+                       GLenum format, GLenum type, HTMLCanvasElement canvas); // May throw DOMException
+    void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 
+                       GLenum format, GLenum type, HTMLVideoElement video); // May throw DOMException
+
+    void uniform1f(WebGLUniformLocation? location, GLfloat x);
+    void uniform1fv(WebGLUniformLocation? location, Float32Array v);
+    void uniform1fv(WebGLUniformLocation? location, sequence<GLfloat> v);
+    void uniform1i(WebGLUniformLocation? location, GLint x);
+    void uniform1iv(WebGLUniformLocation? location, Int32Array v);
+    void uniform1iv(WebGLUniformLocation? location, sequence<long> v);
+    void uniform2f(WebGLUniformLocation? location, GLfloat x, GLfloat y);
+    void uniform2fv(WebGLUniformLocation? location, Float32Array v);
+    void uniform2fv(WebGLUniformLocation? location, sequence<GLfloat> v);
+    void uniform2i(WebGLUniformLocation? location, GLint x, GLint y);
+    void uniform2iv(WebGLUniformLocation? location, Int32Array v);
+    void uniform2iv(WebGLUniformLocation? location, sequence<long> v);
+    void uniform3f(WebGLUniformLocation? location, GLfloat x, GLfloat y, GLfloat z);
+    void uniform3fv(WebGLUniformLocation? location, Float32Array v);
+    void uniform3fv(WebGLUniformLocation? location, sequence<GLfloat> v);
+    void uniform3i(WebGLUniformLocation? location, GLint x, GLint y, GLint z);
+    void uniform3iv(WebGLUniformLocation? location, Int32Array v);
+    void uniform3iv(WebGLUniformLocation? location, sequence<long> v);
+    void uniform4f(WebGLUniformLocation? location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+    void uniform4fv(WebGLUniformLocation? location, Float32Array v);
+    void uniform4fv(WebGLUniformLocation? location, sequence<GLfloat> v);
+    void uniform4i(WebGLUniformLocation? location, GLint x, GLint y, GLint z, GLint w);
+    void uniform4iv(WebGLUniformLocation? location, Int32Array v);
+    void uniform4iv(WebGLUniformLocation? location, sequence<long> v);
+
+    void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, 
+                          Float32Array value);
+    void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, 
+                          sequence<GLfloat> value);
+    void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, 
+                          Float32Array value);
+    void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, 
+                          sequence<GLfloat> value);
+    void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, 
+                          Float32Array value);
+    void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, 
+                          sequence<GLfloat> value);
+
+    void useProgram(WebGLProgram? program);
+    void validateProgram(WebGLProgram? program);
+
+    void vertexAttrib1f(GLuint indx, GLfloat x);
+    void vertexAttrib1fv(GLuint indx, Float32Array values);
+    void vertexAttrib1fv(GLuint indx, sequence<GLfloat> values);
+    void vertexAttrib2f(GLuint indx, GLfloat x, GLfloat y);
+    void vertexAttrib2fv(GLuint indx, Float32Array values);
+    void vertexAttrib2fv(GLuint indx, sequence<GLfloat> values);
+    void vertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z);
+    void vertexAttrib3fv(GLuint indx, Float32Array values);
+    void vertexAttrib3fv(GLuint indx, sequence<GLfloat> values);
+    void vertexAttrib4f(GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+    void vertexAttrib4fv(GLuint indx, Float32Array values);
+    void vertexAttrib4fv(GLuint indx, sequence<GLfloat> values);
+    void vertexAttribPointer(GLuint indx, GLint size, GLenum type, 
+                             GLboolean normalized, GLsizei stride, GLintptr offset);
+
+    void viewport(GLint x, GLint y, GLsizei width, GLsizei height);
+};
+
+interface WebGLRenderingContext implements WebGLRenderingContextBase
+{
+};
+
+
+[Constructor(DOMString type, optional WebGLContextEventInit eventInit)]
+interface WebGLContextEvent : Event {
+    readonly attribute DOMString statusMessage;
+};
+
+// EventInit is defined in the DOM4 specification.
+dictionary WebGLContextEventInit : EventInit {
+    DOMString statusMessage;
+};
diff --git a/dev/disc_collection.py b/dev/disc_collection.py
deleted file mode 100644
index 5cc6e09..0000000
--- a/dev/disc_collection.py
+++ /dev/null
@@ -1,113 +0,0 @@
-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
deleted file mode 100644
index 65eb906..0000000
--- a/dev/visual_experiments.py
+++ /dev/null
@@ -1,33 +0,0 @@
-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/app.rst b/doc/app.rst
index af89ab6..3676325 100644
--- a/doc/app.rst
+++ b/doc/app.rst
@@ -6,7 +6,7 @@ The app module
 
 ----
 
-.. autofunction:: vispy.app.use
+.. autofunction:: vispy.app.use_app
 
 .. autofunction:: vispy.app.create
 
diff --git a/doc/architecture.svg b/doc/architecture.svg
deleted file mode 100644
index 53258f8..0000000
--- a/doc/architecture.svg
+++ /dev/null
@@ -1,689 +0,0 @@
-<?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/color.rst b/doc/color.rst
new file mode 100644
index 0000000..a4e682b
--- /dev/null
+++ b/doc/color.rst
@@ -0,0 +1,6 @@
+================
+The color module
+================
+
+.. automodule:: vispy.color
+    :members:
diff --git a/doc/conf.py b/doc/conf.py
index ab0fae1..23981b2 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -11,7 +11,8 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys, os
+import sys
+import 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
@@ -21,18 +22,18 @@ sys.path.insert(0, os.path.abspath('..'))
 curpath = os.path.dirname(__file__)
 sys.path.append(os.path.abspath('ext'))
 
-# -- General configuration -----------------------------------------------------
+# -- 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',]
+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']
@@ -61,7 +62,7 @@ version = vispy.__version__[:3]
 release = vispy.__version__
 
 
-# -- General configuration -----------------------------------------------------
+# -- General configuration -----------------------------------------------
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -101,7 +102,7 @@ pygments_style = 'sphinx'
 #modindex_common_prefix = []
 
 
-# -- Options for HTML output ---------------------------------------------------
+# -- 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'.
@@ -130,7 +131,7 @@ html_title = 'vispy v%s docs' % version
 # 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']
+html_static_path = []  # '_static']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
@@ -142,9 +143,10 @@ html_static_path = ['_static']
 
 # Custom sidebar templates, maps document names to template names.
 html_sidebars = {
-   '**': ['navigation.html',
-          'localtoc.html',
-          'versions.html'],
+    '**': ['navigation.html',
+           'localtoc.html',
+           #'versions.html'
+          ],
 }
 
 # Additional templates that should be rendered to pages, maps page names to
@@ -181,7 +183,7 @@ html_sidebars = {
 htmlhelp_basename = 'vispydoc'
 
 
-# -- Options for LaTeX output --------------------------------------------------
+# -- Options for LaTeX output --------------------------------------------
 
 # The paper size ('letter' or 'a4').
 #latex_paper_size = 'letter'
@@ -192,8 +194,8 @@ 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'),
+    ('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
@@ -267,8 +269,8 @@ plot_include_source = True
 plot_formats = [('png', 100), ('pdf', 100)]
 
 plot2rst_index_name = 'README'
-plot2rst_rcparams = {'image.cmap' : 'gray',
-                     'image.interpolation' : 'none'}
+plot2rst_rcparams = {'image.cmap': 'gray',
+                     'image.interpolation': 'none'}
 
 # -----------------------------------------------------------------------------
 # intersphinx
diff --git a/doc/event.rst b/doc/event.rst
deleted file mode 100644
index e9c1997..0000000
--- a/doc/event.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-================
-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
index 92b17a4..1b4757c 100644
--- a/doc/ext/docscrape.py
+++ b/doc/ext/docscrape.py
@@ -14,9 +14,11 @@ from warnings import warn
 
 
 class Reader(object):
+
     """A line-based string reader.
 
     """
+
     def __init__(self, data):
         """
         Parameters
@@ -25,10 +27,10 @@ class Reader(object):
            String with lines separated by '\n'.
 
         """
-        if isinstance(data,list):
+        if isinstance(data, list):
             self._str = data
         else:
-            self._str = data.split('\n') # store string as list of lines
+            self._str = data.split('\n')  # store string as list of lines
 
         self.reset()
 
@@ -36,7 +38,7 @@ class Reader(object):
         return self._str[n]
 
     def reset(self):
-        self._l = 0 # current line nr
+        self._l = 0  # current line nr
 
     def read(self):
         if not self.eof():
@@ -63,11 +65,12 @@ class Reader(object):
                 return self[start:self._l]
             self._l += 1
             if self.eof():
-                return self[start:self._l+1]
+                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)
@@ -77,7 +80,7 @@ class Reader(object):
             return (line.strip() and (len(line.lstrip()) == len(line)))
         return self.read_to_condition(is_unindented)
 
-    def peek(self,n=0):
+    def peek(self, n=0):
         if self._l + n < len(self._str):
             return self[self._l + n]
         else:
@@ -88,6 +91,7 @@ class Reader(object):
 
 
 class NumpyDocString(object):
+
     def __init__(self, docstring, config={}):
         docstring = textwrap.dedent(docstring).split('\n')
 
@@ -109,14 +113,14 @@ class NumpyDocString(object):
             'References': '',
             'Examples': '',
             'index': {}
-            }
+        }
 
         self._parse()
 
-    def __getitem__(self,key):
+    def __getitem__(self, key):
         return self._parsed_data[key]
 
-    def __setitem__(self,key,val):
+    def __setitem__(self, key, val):
         if not key in self._parsed_data:
             warn("Unknown section %s" % key)
         else:
@@ -133,25 +137,27 @@ class NumpyDocString(object):
         if l1.startswith('.. index::'):
             return True
 
-        l2 = self._doc.peek(1).strip() #    ---------- or ==========
-        return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1))
+        l2 = self._doc.peek(1).strip()  # ---------- or ==========
+        return l2.startswith('-' * len(l1)) or l2.startswith('=' * len(l1))
 
-    def _strip(self,doc):
+    def _strip(self, doc):
         i = 0
         j = 0
-        for i,line in enumerate(doc):
-            if line.strip(): break
+        for i, line in enumerate(doc):
+            if line.strip():
+                break
 
-        for j,line in enumerate(doc[::-1]):
-            if line.strip(): break
+        for j, line in enumerate(doc[::-1]):
+            if line.strip():
+                break
 
-        return doc[i:len(doc)-j]
+        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
+            if not self._doc.peek(-1).strip():  # previous line was empty
                 section += ['']
 
             section += self._doc.read_to_next_empty_line()
@@ -163,14 +169,14 @@ class NumpyDocString(object):
             data = self._read_to_next_section()
             name = data[0].strip()
 
-            if name.startswith('..'): # index section
+            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):
+    def _parse_param_list(self, content):
         r = Reader(content)
         params = []
         while not r.eof():
@@ -183,13 +189,13 @@ class NumpyDocString(object):
             desc = r.read_to_next_unindented_line()
             desc = dedent_lines(desc)
 
-            params.append((arg_name,arg_type,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
@@ -222,7 +228,8 @@ class NumpyDocString(object):
         rest = []
 
         for line in content:
-            if not line.strip(): continue
+            if not line.strip():
+                continue
 
             m = self._name_rgx.match(line)
             if m and line[m.end():].strip().startswith(':'):
@@ -284,9 +291,10 @@ class NumpyDocString(object):
         self._doc.reset()
         self._parse_summary()
 
-        for (section,content) in self._read_sections():
+        for (section, content) in self._read_sections():
             if not section.startswith('..'):
-                section = ' '.join([s.capitalize() for s in section.split(' ')])
+                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)
@@ -300,17 +308,17 @@ class NumpyDocString(object):
     # string conversion routines
 
     def _str_header(self, name, symbol='-'):
-        return [name, len(name)*symbol]
+        return [name, len(name) * symbol]
 
     def _str_indent(self, doc, indent=4):
         out = []
         for line in doc:
-            out += [' '*indent + line]
+            out += [' ' * indent + line]
         return out
 
     def _str_signature(self):
         if self['Signature']:
-            return [self['Signature'].replace('*','\*')] + ['']
+            return [self['Signature'].replace('*', '\*')] + ['']
         else:
             return ['']
 
@@ -330,7 +338,7 @@ class NumpyDocString(object):
         out = []
         if self[name]:
             out += self._str_header(name)
-            for param,param_type,desc in self[name]:
+            for param, param_type, desc in self[name]:
                 out += ['%s : %s' % (param, param_type)]
                 out += self._str_indent(desc)
             out += ['']
@@ -345,7 +353,8 @@ class NumpyDocString(object):
         return out
 
     def _str_see_also(self, func_role):
-        if not self['See Also']: return []
+        if not self['See Also']:
+            return []
         out = []
         out += self._str_header("See Also")
         last_had_desc = True
@@ -372,7 +381,7 @@ class NumpyDocString(object):
     def _str_index(self):
         idx = self['index']
         out = []
-        out += ['.. index:: %s' % idx.get('default','')]
+        out += ['.. index:: %s' % idx.get('default', '')]
         for section, references in idx.items():
             if section == 'default':
                 continue
@@ -384,11 +393,11 @@ class NumpyDocString(object):
         out += self._str_signature()
         out += self._str_summary()
         out += self._str_extended_summary()
-        for param_list in ('Parameters','Returns','Raises'):
+        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'):
+        for s in ('Notes', 'References', 'Examples'):
             out += self._str_section(s)
         for param_list in ('Attributes', 'Methods'):
             out += self._str_param_list(param_list)
@@ -396,25 +405,28 @@ class NumpyDocString(object):
         return '\n'.join(out)
 
 
-def indent(str,indent=4):
-    indent_str = ' '*indent
+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'
+    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"
+        self._role = role  # e.g. "func" or "meth"
 
         if doc is None:
             if func is None:
@@ -428,7 +440,7 @@ class FunctionDoc(NumpyDocString):
                 # try to read signature
                 argspec = inspect.getargspec(func)
                 argspec = inspect.formatargspec(*argspec)
-                argspec = argspec.replace('*','\*')
+                argspec = argspec.replace('*', '\*')
                 signature = '%s%s' % (func_name, argspec)
             except TypeError as e:
                 signature = '%s()' % func_name
@@ -454,7 +466,7 @@ class FunctionDoc(NumpyDocString):
         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,''),
+            out += '.. %s:: %s\n    \n\n' % (roles.get(self._role, ''),
                                              func_name)
 
         out += super(FunctionDoc, self).__str__(func_role=self._role)
@@ -462,6 +474,7 @@ class FunctionDoc(NumpyDocString):
 
 
 class ClassDoc(NumpyDocString):
+
     def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
                  config={}):
         if not inspect.isclass(cls) and cls is not None:
@@ -491,12 +504,12 @@ class ClassDoc(NumpyDocString):
     def methods(self):
         if self._cls is None:
             return []
-        return [name for name,func in inspect.getmembers(self._cls)
+        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)
+        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
index 9e66c4b..29ad9aa 100644
--- a/doc/ext/docscrape_sphinx.py
+++ b/doc/ext/docscrape_sphinx.py
@@ -1,9 +1,13 @@
-import re, inspect, textwrap, pydoc
+import re
+import inspect
+import textwrap
+import 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)
@@ -18,7 +22,7 @@ class SphinxDocString(NumpyDocString):
     def _str_indent(self, doc, indent=4):
         out = []
         for line in doc:
-            out += [' '*indent + line]
+            out += [' ' * indent + line]
         return out
 
     def _str_signature(self):
@@ -39,11 +43,11 @@ class SphinxDocString(NumpyDocString):
         if self[name]:
             out += self._str_field_list(name)
             out += ['']
-            for param,param_type,desc in self[name]:
+            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 += self._str_indent(desc, 8)
                 out += ['']
         return out
 
@@ -85,7 +89,7 @@ class SphinxDocString(NumpyDocString):
             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
+                hdr = "=" * maxlen_0 + "  " + "=" * maxlen_1 + "  " + "=" * 10
                 fmt = '%%%ds  %%%ds  ' % (maxlen_0, maxlen_1)
                 n_indent = maxlen_0 + maxlen_1 + 4
                 out += [hdr]
@@ -127,7 +131,7 @@ class SphinxDocString(NumpyDocString):
         if len(idx) == 0:
             return out
 
-        out += ['.. index:: %s' % idx.get('default','')]
+        out += ['.. index:: %s' % idx.get('default', '')]
         for section, references in idx.items():
             if section == 'default':
                 continue
@@ -148,9 +152,9 @@ class SphinxDocString(NumpyDocString):
             # Latex collects all references to a separate bibliography,
             # so we need to insert links to it
             if sphinx.__version__ >= "0.6":
-                out += ['.. only:: latex','']
+                out += ['.. only:: latex', '']
             else:
-                out += ['.. latexonly::','']
+                out += ['.. latexonly::', '']
             items = []
             for line in self['References']:
                 m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I)
@@ -188,24 +192,31 @@ class SphinxDocString(NumpyDocString):
         out += self._str_examples()
         for param_list in ('Attributes', 'Methods'):
             out += self._str_member_list(param_list)
-        out = self._str_indent(out,indent)
+        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):
diff --git a/doc/ext/examplesgenerator.py b/doc/ext/examplesgenerator.py
index 20c32f7..7059664 100644
--- a/doc/ext/examplesgenerator.py
+++ b/doc/ext/examplesgenerator.py
@@ -1,4 +1,4 @@
-""" Called from vispy_conf.py to generate the examples for the docs from the 
+""" Called from vispy_conf.py to generate the examples for the docs from the
 example Python files.
 """
 
@@ -11,7 +11,7 @@ import shutil
 try:
     from urllib2 import urlopen
 except ImportError:
-    from urllib.request import urlopen # Py3k
+    from urllib.request import urlopen  # Py3k
 
 
 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -20,7 +20,6 @@ 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):
@@ -32,57 +31,55 @@ def clean():
 
 
 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 = 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 
+    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
+            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
+    count = 0
     for filename, name in examples:
-        print('Writing example %s' % name)
-        
         # Create title
         lines = []
+        # avoid "document isn't included in any toctree" warning
+        lines.append(':orphan:')
+        lines.append('')
         lines.append(name)
-        lines.append('-'*len(lines[-1]))
+        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:
+        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('" '))
@@ -96,39 +93,52 @@ def create_examples(examples):
                         doclines.append(line)
                 else:
                     sourcelines.append('    ' + line)
-        
-        # Add desciprion 
+
+        # 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_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)
+            os.makedirs(output_dir)
         with open(output_filename, 'w') as f:
             f.write('\n'.join(lines))
-    
+        count += 1
+    print('Wrote %s examples.' % count)
 
 
 def create_examples_list(examples):
-    
+
     # Create TOC
     lines = []
-    lines.append('List of examples')
-    lines.append('='*len(lines[-1]))
+    lines.append('Full list of examples')
+    lines.append('=' * len(lines[-1]))
+    lines.append('Check out the `gallery <http://vispy.org/gallery.html>`_ '
+                 'to see what some of these demos look like in action.')
     lines.append('')
-    
+
     # Add entry for each example that we know
-    for _, name in examples:    
-        lines.append('* :doc:`examples/%s`' % name)
-    
+    for _, name in examples:
+        in_gallery = False
+        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 in_gallery:
+            extra = ' [`gallery <http://vispy.org/examples/%s.html>`__]' % name
+        else:
+            extra = ''
+        lines.append('* :doc:`examples/%s`%s' % (name, extra))
+
     # Write file
     with open(os.path.join(DOC_DIR, 'examples.rst'), 'w') as f:
         f.write('\n'.join(lines))
@@ -136,4 +146,3 @@ def create_examples_list(examples):
 
 if __name__ == '__main__':
     main()
-
diff --git a/doc/ext/glapigenerator.py b/doc/ext/glapigenerator.py
index dc08d44..1184ec3 100644
--- a/doc/ext/glapigenerator.py
+++ b/doc/ext/glapigenerator.py
@@ -2,18 +2,18 @@
 """
 
 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 = []
@@ -24,54 +24,39 @@ def parse_api(ob):
         elif name.startswith('gl'):
             func_names.append(name)
     return const_names, func_names
-    
+
 
 def main():
-    
-    lines = []
-    
+
+    lines = [':orphan:', '']
+
     # 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('=' * len(lines[-1]))
     lines.append('')
-    
+
     # Some info
-    lines.append('The ``vispy.gloo.gl`` namespace provides the OpenGL ES 2.0 API,')
+    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('')
-    
+                 (len(gl_const_names), len(gl_func_names)))
+
     # Write class header
     lines.append('**vispy.gloo.gl**\n')
-    
+
     # Write constants and functions
-    for name in sorted(gl_func_names): 
+    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
index 8de9981..599267b 100644
--- a/doc/ext/gloooverviewgenerator.py
+++ b/doc/ext/gloooverviewgenerator.py
@@ -4,6 +4,7 @@ gloo docs. This section is simply added to gloo.__doc__.
 
 from vispy import gloo
 
+
 def main():
     gloo.__doc__ += generate_overview_docs()
 
@@ -11,17 +12,17 @@ def main():
 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
@@ -43,10 +44,10 @@ def get_docs_for_class(klass):
         # Append
         if isinstance(attob, functype):
             funcs.add(' :meth:`~%s.%s.%s`,' % (
-                                    modulename, actualklass.__name__, att))
+                modulename, actualklass.__name__, att))
         elif isinstance(attob, proptype):
             props.add(' :attr:`~%s.%s.%s`,' % (
-                                    modulename, actualklass.__name__, att))
+                modulename, actualklass.__name__, att))
     # Done
     return props, funcs
 
@@ -54,19 +55,12 @@ def get_docs_for_class(klass):
 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,),
-                ]:
+    lines.append('=' * len(lines[-1]))
+    klasseses = ((getattr(gloo, d),) for d in dir(gloo) if d[0].isupper())
+    for klasses in klasseses:
         # Init line
         line = '*'
         for klass in klasses:
@@ -88,7 +82,6 @@ def generate_overview_docs():
             for item in sorted(funcs):
                 line += item
             # Add line, strip last char
-            lines.append(line[:-1]) 
-    
-    return '\n'.join(lines)
+            lines.append(line[:-1])
 
+    return '\n'.join(lines)
diff --git a/doc/ext/numpydoc.py b/doc/ext/numpydoc.py
index 933e212..fbc26c3 100644
--- a/doc/ext/numpydoc.py
+++ b/doc/ext/numpydoc.py
@@ -16,13 +16,17 @@ It will:
 
 """
 
-import os, re, pydoc, sys
+import os
+import re
+import pydoc
+import 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]):
 
@@ -32,14 +36,14 @@ def mangle_docstrings(app, what, name, obj, options, lines,
     if what == 'module':
         # Strip top title
         title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*',
-                              re.I|re.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__:
+            obj.__name__:
         if hasattr(obj, '__module__'):
             v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__))
         else:
@@ -72,21 +76,25 @@ def mangle_docstrings(app, what, name, obj, options, lines,
 
     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__))):
+         '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
+    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_
@@ -109,6 +117,7 @@ from docutils.statemachine import ViewList
 from sphinx.domains.c import CDomain
 from sphinx.domains.python import PythonDomain
 
+
 class ManglingDomainBase(object):
     directive_mangling_map = {}
 
@@ -121,6 +130,7 @@ class ManglingDomainBase(object):
             self.directives[name] = wrap_mangling_directive(
                 self.directives[name], objtype)
 
+
 class NumpyPythonDomain(ManglingDomainBase, PythonDomain):
     name = 'np'
     directive_mangling_map = {
@@ -133,6 +143,7 @@ class NumpyPythonDomain(ManglingDomainBase, PythonDomain):
         'attribute': 'attribute',
     }
 
+
 class NumpyCDomain(ManglingDomainBase, CDomain):
     name = 'np-c'
     directive_mangling_map = {
@@ -143,8 +154,10 @@ class NumpyCDomain(ManglingDomainBase, CDomain):
         'var': 'object',
     }
 
+
 def wrap_mangling_directive(base_directive, objtype):
     class directive(base_directive):
+
         def run(self):
             env = self.state.document.settings.env
 
@@ -163,4 +176,3 @@ def wrap_mangling_directive(base_directive, objtype):
             return base_directive.run(self)
 
     return directive
-
diff --git a/doc/ext/scriptnamemangler.py b/doc/ext/scriptnamemangler.py
index 5e02bd3..bb02dc4 100644
--- a/doc/ext/scriptnamemangler.py
+++ b/doc/ext/scriptnamemangler.py
@@ -14,6 +14,7 @@ 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'),
diff --git a/doc/geometry.rst b/doc/geometry.rst
new file mode 100644
index 0000000..2eb6d1a
--- /dev/null
+++ b/doc/geometry.rst
@@ -0,0 +1,6 @@
+===================
+The geometry module
+===================
+
+.. automodule:: vispy.geometry
+    :members:
diff --git a/doc/gloo.rst b/doc/gloo.rst
index fc70237..d879738 100644
--- a/doc/gloo.rst
+++ b/doc/gloo.rst
@@ -8,12 +8,10 @@ The object oriented OpenGL API (gloo)
 Base class
 ==========
 
-
 .. autoclass:: vispy.gloo.GLObject
     :members:
 
 
-
 Classes related to shaders
 ==========================
 
@@ -31,15 +29,13 @@ Classes related to shaders
     :members:
 
 
-
 Buffer classes
 ==============
 
-
 .. autoclass:: vispy.gloo.VertexBuffer
     :members:
 
-.. autoclass:: vispy.gloo.ElementBuffer
+.. autoclass:: vispy.gloo.IndexBuffer
     :members:
 
 .. autoclass:: vispy.gloo.buffer.DataBuffer
@@ -56,10 +52,7 @@ Texture classes
 
 .. autoclass:: vispy.gloo.Texture3D
 
-.. autoclass:: vispy.gloo.TextureCubeMap
-
-.. autoclass:: vispy.gloo.texture.Texture
-    :members:
+.. autoclass:: vispy.gloo.TextureAtlas
 
 
 Classes related to FBO
@@ -68,16 +61,18 @@ Classes related to FBO
 .. autoclass:: vispy.gloo.FrameBuffer
     :members:
 
-.. autoclass:: vispy.gloo.RenderBuffer
+
+State methods
+=============
+
+.. automodule:: vispy.gloo.wrappers
     :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 also exposes a (low level) functional GL API.
 
  `vispy.gloo.gl docs <gl.html>`_
- 
\ No newline at end of file
+ 
diff --git a/doc/index.rst b/doc/index.rst
index a3aa7e6..5326967 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -1,28 +1,83 @@
-.. 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.
+Vispy is a **high-performance interactive 2D/3D data visualization
+library**. Vispy leverages the computational power of modern **Graphics
+Processing Units (GPUs)** through the **OpenGL** library to display very
+large datasets.
+
+
+Overview
+--------
+
+Vispy is a young library under heavy development at this time. It
+targets two categories of users:
+
+1. **Users knowing OpenGL**, or willing to learn OpenGL, who want to
+   create beautiful and fast interactive 2D/3D visualizations in Python
+   as easily as possible.
+2. **Scientists without any knowledge of OpenGL**, who are seeking a
+   high-level, high-performance plotting toolkit.
+
+If you're in the first category, you can already start using Vispy.
+Vispy offers a Pythonic, NumPy-aware, user-friendly interface for OpenGL
+ES 2.0 called **gloo**. You can focus on writing your GLSL code (a GPU 
+language) instead of dealing with the complicated OpenGL API - Vispy takes 
+care of that automatically for you.
+
+If you're in the second category, we're starting to build experimental
+high-level plotting interfaces. Notably, Vispy now ships a very basic
+and experimental OpenGL backend for matplotlib.
+
 
-This is the auto-generated API documentation for the vispy library. 
-See http://vispy.org for more information on the vispy project.
+Getting started
+---------------
 
-Contents:
+We are still working on a complete user guide for Vispy. In the meantime, you
+can:
+
+  * Check out the `gallery <http://vispy.org/gallery.html>`_
+  * Use the ``mpl_plot`` experimental OpenGL backend for matplotlib
+  * Start learning OpenGL (see below)
+  * Write your own visualizations with **gloo** (require knowing some 
+    OpenGL/GLSL)
+  * Start using the higher-level interfaces (visuals, scene graph)
+  
+
+Learning the fundamentals of modern OpenGL
+------------------------------------------
+
+Vispy will eventually provide high-level facilities to let scientists create
+high-quality, high-performance plots without any knowledge of OpenGL. In the 
+meantime, you can learn more about modern OpenGL in the references below.
+
+Even when Vispy is mature enough, knowing OpenGL will still let you write 
+entirely custom interactive visualizations that fully leverage the power of
+GPUs.
+
+  * `A tutorial about Vispy <http://ipython-books.github.io/featured-06.html>`_, by Cyrille Rossant, published in the `IPython Cookbook <http://ipython-books.github.io/>`_
+  * `A tutorial about modern OpenGL and Vispy, by Nicolas Rougier <http://www.loria.fr/~rougier/teaching/opengl/>`_
+  * A paper on the fundamentals behing Vispy: `Rossant C and Harris KD, Hardware-accelerated interactive data visualization for neuroscience in Python, Frontiers in Neuroinformatics 2013 <http://journal.frontiersin.org/Journal/10.3389/fninf.2013.00036/full>`_
+  * A free online book on modern OpenGL (but not Python): `Learning Modern 3D Graphics Programming, by Jason L. McKesson <http://www.arcsynthesis.org/gltut/>`_
+  * `A PyOpenGL tutorial <http://cyrille.rossant.net/2d-graphics-rendering-tutorial-with-pyopengl/>`_
+  * `A tutorial on OpenGL shaders <http://cyrille.rossant.net/shaders-opengl/>`_
+
+
+API reference
+-------------
 
 .. toctree::
    :maxdepth: 2
    
-   vispy.util - Utilities <util>
-   vispy.app - Application API <app>
-   vispy.gloo - object oriented GL API <gloo>
-   vispy.visuals <visuals>
+   vispy - Top-level tools <vispy>
+   vispy.app - Application, event loops, canvas, backends <app>
+   vispy.color - Handling colors <color>
+   vispy.geometry - Visualization-related geometry routines <geometry>
+   vispy.gloo - User-friendly, Pythonic, object-oriented interface to OpenGL <gloo>
+   vispy.io - Data IO <io>
+   vispy.mpl_plot - OpenGL backend for matplotlib [experimental] <mpl_plot>
+   vispy.scene - The system underlying the upcoming high-level visualization interfaces [highly experimental] <scene>
+   vispy.util - Miscellaneous utilities <util>
    
    examples
    releasenotes
@@ -34,4 +89,3 @@ Indices and tables
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-
diff --git a/doc/io.rst b/doc/io.rst
new file mode 100644
index 0000000..57a42e2
--- /dev/null
+++ b/doc/io.rst
@@ -0,0 +1,6 @@
+==================
+The data IO module
+==================
+
+.. automodule:: vispy.io
+    :members:
diff --git a/doc/mpl_plot.rst b/doc/mpl_plot.rst
new file mode 100644
index 0000000..d04e580
--- /dev/null
+++ b/doc/mpl_plot.rst
@@ -0,0 +1,6 @@
+===================
+The MPL plot module
+===================
+
+.. automodule:: vispy.mpl_plot
+    :members:
diff --git a/doc/plot.rst b/doc/plot.rst
new file mode 100644
index 0000000..0d6cce1
--- /dev/null
+++ b/doc/plot.rst
@@ -0,0 +1,6 @@
+================================
+The vispy native plotting module
+================================
+
+.. automodule:: vispy.plot
+    :members:
diff --git a/doc/releasenotes.rst b/doc/releasenotes.rst
index 3ebbb4f..9caa82b 100644
--- a/doc/releasenotes.rst
+++ b/doc/releasenotes.rst
@@ -2,8 +2,23 @@
 Release notes
 =============
 
-**Vispy 0.2**
+**Vispy 0.3**
+
+Many changes:
+  * Added multiple new application backends, including a IPython browser
+    backend.
+  * Experimental support for high-level visualizations through
+    '`vispy.scene`` and ``vispy.scene.visuals``.
+  * Experimental support for matplotlib plotting through ``vispy.mpl_plot``.
+  * Loads of bugfixes.
+
+
+**Vispy 0.2.1**
 
+Small fix in the setup script. The buf prevented pip from working.
+
+
+**Vispy 0.2**
 
 In this release we focussed on improving and finalizing the object
 oriented OpenGL interface ``vispy.gloo``. Some major (backward
@@ -25,6 +40,5 @@ Changes in more detail:
 
 **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/scene.rst b/doc/scene.rst
new file mode 100644
index 0000000..0879389
--- /dev/null
+++ b/doc/scene.rst
@@ -0,0 +1,45 @@
+===========
+Scene graph
+===========
+
+.. automodule:: vispy.scene
+   :members:
+
+----
+
+vispy.scene.cameras
+-------------------
+.. automodule:: vispy.scene.cameras
+   :members:
+
+
+----
+
+vispy.scene.shaders
+-------------------
+.. automodule:: vispy.scene.shaders
+   :members:
+
+
+----
+
+vispy.scene.transforms
+----------------------
+.. automodule:: vispy.scene.transforms
+   :members:
+
+
+----
+
+vispy.scene.visuals
+-------------------
+.. automodule:: vispy.scene.visuals
+   :members:
+
+
+----
+
+vispy.scene.widgets
+-------------------
+.. automodule:: vispy.scene.widgets
+   :members:
diff --git a/doc/shaders.svg b/doc/shaders.svg
deleted file mode 100644
index b912bef..0000000
--- a/doc/shaders.svg
+++ /dev/null
@@ -1,1318 +0,0 @@
-<?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
index 1c6163a..750ccc4 100644
--- a/doc/themes/scikit-image/layout.html
+++ b/doc/themes/scikit-image/layout.html
@@ -2,7 +2,8 @@
     scikit-image/layout.html
     ~~~~~~~~~~~~~~~~~
 
-    Sphinx layout template for the scikit-image theme
+    Sphinx layout template for the scikit-image theme, written by
+    Johannes Schönberger.
 
 #}
 
@@ -74,7 +75,7 @@
         <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">
+    <link rel="shortcut icon" href="{{ pathto('_static/', 1) }}favicon.ico">
     {%- block extrahead %}{% endblock %}
 </head>
 <body class="container">
@@ -85,7 +86,7 @@
             <ul class="nav">
                 {% include 'navbar.html' %}
             </ul>
-            <form class="navbar-form pull-right" action="search.html" method="get">
+            <form class="navbar-form pull-right" action="{{ pathto('search') }}" 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" >
@@ -104,9 +105,8 @@
     </div>
     <div class="well footer">
         <small>
-            © Copyright the vispy development team.
+            © Copyright the scikit-image 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>
diff --git a/doc/themes/scikit-image/search.html b/doc/themes/scikit-image/search.html
index 3a723ef..89464ab 100644
--- a/doc/themes/scikit-image/search.html
+++ b/doc/themes/scikit-image/search.html
@@ -10,14 +10,19 @@
 {% extends "layout.html" %}
 {% set title = _('Search') %}
 {% set script_files = script_files + ['_static/searchtools.js'] %}
-{% set script_files = script_files + ['searchindex.js'] %}
+{% block extrahead %}
+  <script type="text/javascript">
+    jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
+  </script>
+  {# this is used when loading the search index using $.ajax fails,
+     such as on Chrome for documents on localhost #}
+  <script type="text/javascript" id="searchindexloader"></script>
+  {{ super() }}
+{% endblock %}
 {% block body %}
   <h1 id="search-documentation">{{ _('Search') }}</h1>
   <div id="fallback" class="admonition warning">
-  <script type="text/javascript">
-    $('#fallback').hide();
-    $("input.search").focus();
-  </script>
+  <script type="text/javascript">$('#fallback').hide();</script>
   <p>
     {% trans %}Please activate JavaScript to enable the search
     functionality.{% endtrans %}
@@ -25,11 +30,17 @@
   </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
+    words into the box below and click "search". 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>
+  <form action="" method="get">
+    <input type="text" name="q" value="" />
+    <input type="submit" value="{{ _('search') }}" />
+    <span id="search-progress" style="padding-left: 10px"></span>
+  </form>
   {% if search_performed %}
+    <h2>{{ _('Search Results') }}</h2>
     {% if not search_results %}
       <p>{{ _('Your search did not match any results.') }}</p>
     {% endif %}
@@ -38,9 +49,11 @@
   {% if search_results %}
     <ul>
     {% for href, caption, context in search_results %}
-      <li class="well"><a href="{{ pathto(item.href) }}">{{ caption }}</a></li>
+      <li><a href="{{ pathto(item.href) }}">{{ caption }}</a>
+        <div class="context">{{ context|e }}</div>
+      </li>
     {% endfor %}
     </ul>
   {% endif %}
   </div>
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/doc/themes/scikit-image/static/css/custom.css b/doc/themes/scikit-image/static/css/custom.css
index 2d64dbb..0a2d313 100644
--- a/doc/themes/scikit-image/static/css/custom.css
+++ b/doc/themes/scikit-image/static/css/custom.css
@@ -41,6 +41,12 @@ h6 {
     font-size: 13px;
     line-height: 15px;
 }
+blockquote {
+    border-left: 0;
+}
+dt {
+    font-weight: normal;
+}
 
 .logo {
     float: left;
@@ -51,21 +57,18 @@ h6 {
 }
 
 .hero {
-    padding: 10px 25px 15px 25px;
+    padding: 10px 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;
 }
 
@@ -76,6 +79,10 @@ h6 {
     padding-left: 15px;
 }
 
+#current {
+    font-weight: bold;
+}
+
 .headerlink {
     margin-left: 10px;
     color: #ddd;
@@ -226,7 +233,7 @@ p.admonition-title {
     text-align: center !important;
 }
 
-.summary-box {
-    /* Should derive width from span8 */
-    width: 640px;
+/* misc */
+div.math {
+    text-align: center;
 }
diff --git a/doc/themes/scikit-image/static/js/docversions.js b/doc/themes/scikit-image/static/js/docversions.js
deleted file mode 100644
index 951ce3e..0000000
--- a/doc/themes/scikit-image/static/js/docversions.js
+++ /dev/null
@@ -1,21 +0,0 @@
-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/util.rst b/doc/util.rst
index 93ac41b..6f85c00 100644
--- a/doc/util.rst
+++ b/doc/util.rst
@@ -7,33 +7,54 @@ The util module
 
 ----
 
-vispy.util.keys
+vispy.util.event
 ----------------
+.. automodule:: vispy.util.event
+
+----
+
+.. autoclass:: vispy.util.event.Event
+    :members:
+
+.. autoclass:: vispy.app.canvas.MouseEvent
+    :members:
+
+.. autoclass:: vispy.app.canvas.KeyEvent
+    :members:
 
-Constants for keys like control and alt.
+.. autoclass:: vispy.app.canvas.ResizeEvent
+    :members:
 
+.. autoclass:: vispy.app.canvas.DrawEvent
+    :members:
 
 ----
 
-vispy.util.transforms
----------------------
-.. automodule:: vispy.util.transforms
+.. autoclass:: vispy.util.event.EventEmitter
     :members:
+    
+    .. automethod:: vispy.util.event.EventEmitter.__call__
 
 
 ----
 
-vispy.util.event
+vispy.util.fonts
 ----------------
+.. automodule:: vispy.util.fonts
+    :members:
 
-.. automodule:: vispy.util.event
 
+----
 
-.. autoclass:: vispy.util.event.EventEmitter
+vispy.util.keys
+---------------
+.. automodule:: vispy.util.keys
     :members:
-    
-    .. automethod:: vispy.util.event.EventEmitter.__call__
 
 
-.. autoclass:: vispy.util.event.EmitterGroup
+----
+
+vispy.util.transforms
+---------------------
+.. automodule:: vispy.util.transforms
     :members:
diff --git a/doc/vispy.rst b/doc/vispy.rst
new file mode 100644
index 0000000..cc073d2
--- /dev/null
+++ b/doc/vispy.rst
@@ -0,0 +1,6 @@
+=====
+Vispy
+=====
+
+.. automodule:: vispy
+    :members:
diff --git a/doc/visuals.rst b/doc/visuals.rst
deleted file mode 100644
index fb7b99c..0000000
--- a/doc/visuals.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-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-glut.py b/examples/app/app-glut.py
deleted file mode 100644
index 1500082..0000000
--- a/examples/app/app-glut.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/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
deleted file mode 100644
index bd508bf..0000000
--- a/examples/app/app-pyglet.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/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
deleted file mode 100644
index 983cdee..0000000
--- a/examples/app/app-qt.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# #!/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/howto/animate-images.py b/examples/basics/gloo/animate_images.py
similarity index 55%
rename from examples/howto/animate-images.py
rename to examples/basics/gloo/animate_images.py
index 9c9df67..9cb08cf 100644
--- a/examples/howto/animate-images.py
+++ b/examples/basics/gloo/animate_images.py
@@ -1,26 +1,24 @@
 #!/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)
+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 = 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]])
 
@@ -51,7 +49,7 @@ FRAG_SHADER = """
 uniform sampler2D u_texture;
 varying vec2 v_texcoord;
 void main()
-{    
+{
     gl_FragColor = texture2D(u_texture, v_texcoord);
     gl_FragColor.a = 1.0;
 }
@@ -60,55 +58,56 @@ void main()
 
 
 class Canvas(app.Canvas):
-    
+
     def __init__(self):
-        app.Canvas.__init__(self)
-        self.size = W*5,H*5
+        app.Canvas.__init__(self, keys='interactive')
+        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.texture.interpolation = 'linear'
+
         self.program['u_texture'] = self.texture
-        self.program.set_vars(gloo.VertexBuffer(data))
+        self.program.bind(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.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
-    
+        
+        self._timer = app.Timer('auto', connect=self.update, start=True)
+
     def on_initialize(self, event):
-        gl.glClearColor(1,1,1,1)
-    
+        gloo.set_clear_color('white')
+
     def on_resize(self, event):
         width, height = event.size
-        gl.glViewport(0, 0, width, height)
-        self.projection = ortho( 0, width, 0, height, -100, 100 )
+        gloo.set_viewport(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)
+        # 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)
+            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)
+            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.bind(gloo.VertexBuffer(data))
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+        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()
+        self.program.draw('triangle_strip')
 
 
 if __name__ == '__main__':
diff --git a/examples/basics/gloo/animate_images_slice.py b/examples/basics/gloo/animate_images_slice.py
new file mode 100644
index 0000000..b183f40
--- /dev/null
+++ b/examples/basics/gloo/animate_images_slice.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# author: Irwin Zaid
+# vispy: gallery 2
+
+"""
+Example demonstrating a 3D Texture. The volume contains noise that
+is smoothed in the z-direction. Shown is one slice throught that volume
+to give the effect of "morphing" noise.
+"""
+
+import numpy as np
+
+from vispy.util.transforms import ortho
+from vispy import gloo
+from vispy import app
+
+
+# Shape of image to be displayed
+D, H, W = 30, 60, 90
+
+# Modulated image
+I = np.random.uniform(0, 0.1, (D, H, W, 3)).astype(np.float32)
+# Depth slices are dark->light
+I[...] += np.linspace(0, 0.9, D)[:, np.newaxis, np.newaxis, np.newaxis]
+# Make vertical direction more green moving upward
+I[..., 1] *= np.linspace(0, 1, H)[np.newaxis, :, np.newaxis]
+# Make horizontal direction more red moving rightward
+I[..., 0] *= np.linspace(0, 1, W)[np.newaxis, np.newaxis, :]
+
+# 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;
+
+// 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 sampler3D u_texture;
+uniform float i;
+varying vec2 v_texcoord;
+void main()
+{
+    // step through gradient with i, note that slice (depth) comes last here!
+    gl_FragColor = texture3D(u_texture, vec3(v_texcoord, i));
+    gl_FragColor.a = 1.0;
+}
+
+"""
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+        self.size = W * 5, H * 5
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.texture = gloo.Texture3D(I)
+        self.texture.interpolation = 'nearest'
+        self.texture.wrapping = 'clamp_to_edge'
+
+        self.program['u_texture'] = self.texture
+        self.program['i'] = 0.0
+        self.program.bind(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
+
+        self.i = 0
+
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_initialize(self, event):
+        gloo.set_clear_color('white')
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        self.projection = ortho(0, width, 0, height, -100, 100)
+        self.program['u_projection'] = self.projection
+
+        # Compute the 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.bind(gloo.VertexBuffer(data))
+
+    def on_timer(self, event):
+        # cycle every 2 sec
+        self.i = (self.i + 1./120.) % 1.0
+        self.update()
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+        self.program['i'] = 1.9 * np.abs(0.5 - self.i)
+        self.program.draw('triangle_strip')
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/howto/animate-shape.py b/examples/basics/gloo/animate_shape.py
similarity index 54%
rename from examples/howto/animate-shape.py
rename to examples/basics/gloo/animate_shape.py
index de7ff78..aac9b06 100644
--- a/examples/howto/animate-shape.py
+++ b/examples/basics/gloo/animate_shape.py
@@ -1,8 +1,8 @@
-# #!/usr/bin/env python
+# !/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.
@@ -13,27 +13,26 @@ 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
+im1 = np.zeros((100, 100, 3), 'float32')
+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] ])
+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)
+indices = np.array([0, 1, 2, 1, 2, 3], np.uint16)
+indices_buffer = gloo.IndexBuffer(indices)
+client_indices_buffer = gloo.IndexBuffer(indices)
 
 
 VERT_SHADER = """ // simple vertex shader
@@ -41,7 +40,6 @@ 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
@@ -56,7 +54,7 @@ FRAG_SHADER = """ // simple fragment shader
 uniform sampler2D texture1;
 
 void main()
-{    
+{
     gl_FragColor = texture2D(texture1, gl_TexCoord[0].st);
 }
 
@@ -64,48 +62,46 @@ void main()
 
 
 class Canvas(app.Canvas):
-    
+
     def __init__(self):
-        app.Canvas.__init__(self)
-        
+        app.Canvas.__init__(self, keys='interactive')
+
         # Create program
-        self._program = gloo.Program( VERT_SHADER, FRAG_SHADER)
-        
+        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.bind(self._vbo)  # This does:
         #self._program['a_position'] = self._vbo['a_position']
         #self._program['a_texcoords'] = self._vbo['a_texcoords']
         
+        self._timer = app.Timer('auto', connect=self.update, start=True)
     
     def on_initialize(self, event):
-        gl.glClearColor(1,1,1,1)
-    
-    
+        gloo.set_clear_color('white')
+
     def on_resize(self, event):
         width, height = event.size
-        gl.glViewport(0, 0, width, height)
-    
-    
-    def on_paint(self, event):
-        
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+
         # Clear
-        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
-        
+        gloo.clear()
+
         # Draw
-        self._program['sizeFactor'] = 0.5 + np.sin(time.time()*3)*0.2
-        
+        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()
+        # self._program.draw('triangle_strip')
+        self._program.draw('triangles', indices_buffer)
+        # self._program.draw('triangles', client_indices_buffer)  # Not
+        # recommended
 
 
 if __name__ == '__main__':
diff --git a/examples/howto/display-lines.py b/examples/basics/gloo/display_lines.py
similarity index 59%
rename from examples/howto/display-lines.py
rename to examples/basics/gloo/display_lines.py
index 93fc28a..2983495 100644
--- a/examples/howto/display-lines.py
+++ b/examples/basics/gloo/display_lines.py
@@ -1,8 +1,7 @@
-# #!/usr/bin/env python
+# !/usr/bin/env python
 # -*- coding: utf-8 -*-
 # vispy: gallery 2
-
-""" Show a bunch of lines. 
+""" Show a bunch of lines.
 This example demonstrates how multiple line-pieces can be drawn
 using one call, by discarting some fragments.
 """
@@ -10,16 +9,15 @@ 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')
+# app.use_app('glut')
 
-# Create vetices 
+# 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)
+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 = """
@@ -41,7 +39,7 @@ void main()
 {
     float f = fract(v_id);
     // The second useless test is needed on OSX 10.8 (fuck)
-    if( (f > 0.0001) && (f < .9999f) )
+    if( (f > 0.0001) && (f < .9999) )
         discard;
     else
         gl_FragColor = vec4(0,0,0,1);
@@ -49,87 +47,75 @@ void main()
 """
 
 
-
 class Canvas(app.Canvas):
 
     # ---------------------------------
     def __init__(self):
-        app.Canvas.__init__(self)
+        app.Canvas.__init__(self, keys='interactive')
 
         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.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)
+        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()
-
-
+        self.timer = app.Timer('auto', connect=self.on_timer)
 
     # ---------------------------------
     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)
-
+        gloo.set_clear_color('white')
+        gloo.set_state('translucent')
 
     # ---------------------------------
-    def on_key_press(self,event):
+    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):
+    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)
+        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 )
+        gloo.set_viewport(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.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)
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('line_strip')
 
 
 if __name__ == '__main__':
diff --git a/examples/howto/display-points.py b/examples/basics/gloo/display_points.py
similarity index 67%
rename from examples/howto/display-points.py
rename to examples/basics/gloo/display_points.py
index 13ecfe7..c9db79f 100644
--- a/examples/howto/display-points.py
+++ b/examples/basics/gloo/display_points.py
@@ -1,20 +1,18 @@
-# #!/usr/bin/env python
+# !/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 
+# 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)
+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;
@@ -40,14 +38,16 @@ void main (void) {
 """
 
 FRAG_SHADER = """
+#version 120
+
 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 size = 2.0*(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;
@@ -66,36 +66,26 @@ void main()
 """
 
 
-
 class Canvas(app.Canvas):
+
     def __init__(self):
-        app.Canvas.__init__(self)
+        app.Canvas.__init__(self, keys='interactive')
 
+    def on_initialize(self, event):
         self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
-        
         # Set uniform and attribute
-        self.program['a_color']    = gloo.VertexBuffer(v_color)
+        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)
-        
+        self.program['a_size'] = gloo.VertexBuffer(v_size)
+        gloo.set_state(clear_color='white', blend=True,
+                       blend_func=('src_alpha', '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)
+        gloo.set_viewport(0, 0, *event.size)
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+        self.program.draw('points')
 
 
 if __name__ == '__main__':
diff --git a/examples/howto/display-shape.py b/examples/basics/gloo/display_shape.py
similarity index 63%
rename from examples/howto/display-shape.py
rename to examples/basics/gloo/display_shape.py
index e3c0060..ce7e385 100644
--- a/examples/howto/display-shape.py
+++ b/examples/basics/gloo/display_shape.py
@@ -1,20 +1,19 @@
-# #!/usr/bin/env python
+# !/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)
+# Create vertices
+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
@@ -27,38 +26,35 @@ void main (void) {
 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)
-        
+        app.Canvas.__init__(self, keys='interactive')
+
         # 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)
-    
-    
+        gloo.set_clear_color('white')
+
     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)
-    
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+        gloo.clear()
+        self._program.draw('triangle_strip')
+
 
 if __name__ == '__main__':
     c = Canvas()
diff --git a/examples/basics/gloo/gpuimage.py b/examples/basics/gloo/gpuimage.py
new file mode 100644
index 0000000..9a3515f
--- /dev/null
+++ b/examples/basics/gloo/gpuimage.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# vispy: gallery 100
+"""
+Illustrate how to plot a 2D function (an image) y=f(x,y) on the GPU.
+"""
+
+from vispy import app, gloo
+
+vertex = """
+attribute vec2 a_position;
+varying vec2 v_position;
+void main()
+{
+    gl_Position = vec4(a_position, 0.0, 1.0);
+    v_position = a_position;
+}
+"""
+
+fragment = """
+const float M_PI = 3.14159265358979323846;
+uniform float u_time;
+varying vec2 v_position;
+
+/**********************************************************
+Specify the parameters here.
+**********************************************************/
+const float z_offset = 1.;  // (z+z_offset)/z_max should be in [0,1]
+const float z_max = 2.;
+const float x_scale = 5.;  // x is between -x_scale and +x_scale
+const float y_scale = 5.; // y is between -y_scale and +y_scale
+const float t_scale = 5.; // scale for the time
+/*********************************************************/
+
+float f(float x, float y, float t) {
+
+    // x is in [-x_scale, +x_scale]
+    // y is in [-y_scale, +y_scale]
+    // t is in [0, +oo)
+
+    /**********************************************************
+    Write your function below.
+    **********************************************************/
+
+    float k = .25*cos(t);
+    return (cos(x)+k)*(sin(y)-k);
+
+    /*********************************************************/
+
+}
+
+vec4 jet(float x) {
+    vec3 a, b;
+    float c;
+    if (x < 0.34) {
+        a = vec3(0, 0, 0.5);
+        b = vec3(0, 0.8, 0.95);
+        c = (x - 0.0) / (0.34 - 0.0);
+    } else if (x < 0.64) {
+        a = vec3(0, 0.8, 0.95);
+        b = vec3(0.85, 1, 0.04);
+        c = (x - 0.34) / (0.64 - 0.34);
+    } else if (x < 0.89) {
+        a = vec3(0.85, 1, 0.04);
+        b = vec3(0.96, 0.7, 0);
+        c = (x - 0.64) / (0.89 - 0.64);
+    } else {
+        a = vec3(0.96, 0.7, 0);
+        b = vec3(0.5, 0, 0);
+        c = (x - 0.89) / (1.0 - 0.89);
+    }
+    return vec4(mix(a, b, c), 1.0);
+}
+
+void main() {
+    vec2 pos = v_position;
+    float z = f(x_scale * pos.x, y_scale * pos.y, t_scale * u_time);
+    gl_FragColor = jet((z + z_offset) / (z_max));
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, position=(300, 100),
+                            size=(800, 800), keys='interactive')
+
+        self.program = gloo.Program(vertex, fragment)
+        self.program['a_position'] = [(-1., -1.), (-1., +1.),
+                                      (+1., -1.), (+1., +1.)]
+
+        self.timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_timer(self, event):
+        self.program['u_time'] = event.elapsed
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+        self.program.draw('triangle_strip')
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/howto/hello-fbo.py b/examples/basics/gloo/hello_fbo.py
similarity index 53%
rename from examples/howto/hello-fbo.py
rename to examples/basics/gloo/hello_fbo.py
index 557bdf5..ccfc2e5 100644
--- a/examples/howto/hello-fbo.py
+++ b/examples/basics/gloo/hello_fbo.py
@@ -1,8 +1,8 @@
-# #!/usr/bin/env python
+# !/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.
 """
@@ -10,16 +10,15 @@ 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)
+# 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 = """
@@ -32,7 +31,7 @@ void main (void) {
 FRAG_SHADER1 = """
 uniform vec4 u_color;
 void main()
-{    
+{
     gl_FragColor = u_color;
 }
 """
@@ -56,11 +55,11 @@ 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);
+{
+    float scalefactor = 1.0 / float(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++) { 
+        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;
@@ -72,57 +71,53 @@ void main()
 
 SIZE = 50
 
+
 class Canvas(app.Canvas):
-    
+
     def __init__(self):
-        app.Canvas.__init__(self)
+        app.Canvas.__init__(self, keys='interactive')
         self.size = 560, 420
-        
+
         # Create texture to render to
-        self._rendertex = gloo.Texture2D()
-        
+        shape = self.size[1], self.size[0]
+        self._rendertex = gloo.Texture2D(shape=(shape + (3,)),
+                                         dtype=np.float32)
+
         # Create FBO, attach the color buffer and depth buffer
-        self._fbo = gloo.FrameBuffer(self._rendertex, gloo.RenderBuffer())
-        
+        self._fbo = gloo.FrameBuffer(self._rendertex,
+                                     gloo.DepthBuffer(shape))
+
         # Create program to render a shape
-        self._program1 = gloo.Program(  gloo.VertexShader(VERT_SHADER1), 
-                                        gloo.FragmentShader(FRAG_SHADER1) )
+        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 = 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['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)
-        
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
         # 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)
-        
+            gloo.set_clear_color((0.0, 0.0, 0.5, 1))
+            gloo.clear(color=True, depth=True)
+            gloo.set_viewport(0, 0, *self.size)
+            self._program1.draw('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)
-    
+        gloo.set_clear_color('white')
+        gloo.clear(color=True, depth=True)
+        self._program2.draw('triangle_strip')
+
 
 if __name__ == '__main__':
     c = Canvas()
diff --git a/examples/basics/gloo/post_processing.py b/examples/basics/gloo/post_processing.py
new file mode 100644
index 0000000..34edabb
--- /dev/null
+++ b/examples/basics/gloo/post_processing.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author:   Nicolas P .Rougier
+# Date:     04/03/2014
+# Abstract: Show post-processing technique using framebuffer
+# Keywords: framebuffer, gloo, cube, post-processing
+# -----------------------------------------------------------------------------
+
+import numpy as np
+from vispy import app
+
+from vispy.geometry import create_cube
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.gloo import (Program, VertexBuffer, IndexBuffer, Texture2D, clear,
+                        FrameBuffer, DepthBuffer, set_viewport, set_state)
+
+cube_vertex = """
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+attribute vec3 position;
+attribute vec2 texcoord;
+attribute vec3 normal;
+attribute vec4 color;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = projection * view * model * vec4(position,1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+cube_fragment = """
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+void main()
+{
+    float r = texture2D(texture, v_texcoord).r;
+    gl_FragColor = vec4(r,r,r,1);
+}
+"""
+
+quad_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+quad_fragment = """
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+void main()
+{
+    vec2 d = 5.0 * vec2(sin(v_texcoord.y*50.0),0)/512.0;
+
+    // Inverse video
+    if( v_texcoord.x > 0.5 ) {
+        gl_FragColor.rgb = 1.0-texture2D(texture, v_texcoord+d).rgb;
+    } else {
+        gl_FragColor = texture2D(texture, v_texcoord);
+    }
+}
+"""
+
+
+def checkerboard(grid_num=8, grid_size=32):
+    row_even = grid_num // 2 * [0, 1]
+    row_odd = grid_num // 2 * [1, 0]
+    Z = np.row_stack(grid_num // 2 * (row_even, row_odd)).astype(np.uint8)
+    return 255 * Z.repeat(grid_size, axis=0).repeat(grid_size, axis=1)
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, title='Framebuffer post-processing',
+                            keys='interactive', size=(512, 512))
+
+    def on_initialize(self, event):
+        # Build cube data
+        # --------------------------------------
+        vertices, indices, _ = create_cube()
+        vertices = VertexBuffer(vertices)
+        self.indices = IndexBuffer(indices)
+
+        # Build program
+        # --------------------------------------
+        view = np.eye(4, dtype=np.float32)
+        model = np.eye(4, dtype=np.float32)
+        translate(view, 0, 0, -7)
+        self.phi, self.theta = 60, 20
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+
+        self.cube = Program(cube_vertex, cube_fragment)
+        self.cube.bind(vertices)
+        self.cube["texture"] = checkerboard()
+        self.cube["texture"].interpolation = 'linear'
+        self.cube['model'] = model
+        self.cube['view'] = view
+
+        depth = DepthBuffer((512, 512))
+        color = Texture2D(shape=(512, 512, 3), dtype=np.dtype(np.float32))
+        self.framebuffer = FrameBuffer(color=color, depth=depth)
+
+        self.quad = Program(quad_vertex, quad_fragment, count=4)
+        self.quad['texcoord'] = [(0, 0), (0, 1), (1, 0), (1, 1)]
+        self.quad['position'] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        self.quad['texture'] = color
+        self.quad["texture"].interpolation = 'linear'
+
+        # OpenGL and Timer initalization
+        # --------------------------------------
+        set_state(clear_color=(.3, .3, .35, 1), depth_test=True)
+        self.timer = app.Timer('auto', connect=self.on_timer, start=True)
+        self._set_projection(self.size)
+
+    def on_draw(self, event):
+        self.framebuffer.activate()
+        set_viewport(0, 0, 512, 512)
+        clear(color=True, depth=True)
+        set_state(depth_test=True)
+        self.cube.draw('triangles', self.indices)
+        self.framebuffer.deactivate()
+        clear(color=True)
+        set_state(depth_test=False)
+        self.quad.draw('triangle_strip')
+
+    def on_resize(self, event):
+        self._set_projection(event.size)
+
+    def _set_projection(self, size):
+        width, height = size
+        set_viewport(0, 0, width, height)
+        projection = perspective(30.0, width / float(height), 2.0, 10.0)
+        self.cube['projection'] = projection
+
+    def on_timer(self, event):
+        self.theta += .5
+        self.phi += .5
+        model = np.eye(4, dtype=np.float32)
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        self.cube['model'] = model
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    c.app.run()
diff --git a/examples/basics/gloo/rotate_cube.py b/examples/basics/gloo/rotate_cube.py
new file mode 100644
index 0000000..b20dbdb
--- /dev/null
+++ b/examples/basics/gloo/rotate_cube.py
@@ -0,0 +1,170 @@
+#!/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.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;
+attribute vec3 a_normal;
+
+// 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, dtype=np.uint32), 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, dtype=np.uint32), 8)
+
+    return V, I1, I2
+
+
+# -----------------------------------------------------------------------------
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+        self.size = 800, 600
+
+        self.vertices, self.filled, self.outline = cube()
+        self.filled_buf = gloo.IndexBuffer(self.filled)
+        self.outline_buf = gloo.IndexBuffer(self.outline)
+
+        self.program = gloo.Program(vert, frag)
+        self.program.bind(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('auto', connect=self.on_timer, start=True)
+    
+    # ---------------------------------
+    def on_initialize(self, event):
+        gloo.set_clear_color('white')
+        gloo.set_state('opaque')
+        gloo.set_polygon_offset(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
+        gloo.set_viewport(0, 0, width, height)
+        self.projection = perspective(45.0, width / float(height), 2.0, 10.0)
+        self.program['u_projection'] = self.projection
+
+    # ---------------------------------
+    def on_draw(self, event):
+        gloo.clear()
+
+        # Filled cube
+        
+        gloo.set_state(blend=False, depth_test=True, polygon_offset_fill=True)
+        self.program['u_color'] = 1, 1, 1, 1
+        self.program.draw('triangles', self.filled_buf)
+
+        # Outline
+        gloo.set_state(blend=True, depth_test=True, polygon_offset_fill=False)
+        gloo.set_depth_mask(False)
+        self.program['u_color'] = 0, 0, 0, 1
+        self.program.draw('lines', self.outline_buf)
+        gloo.set_depth_mask(True)
+
+
+# -----------------------------------------------------------------------------
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/basics/gloo/start.py b/examples/basics/gloo/start.py
new file mode 100644
index 0000000..bc850b7
--- /dev/null
+++ b/examples/basics/gloo/start.py
@@ -0,0 +1,18 @@
+# !/usr/bin/env python
+# -*- coding: utf-8 -*-
+""" Probably the simplest vispy example
+"""
+
+from vispy import app
+from vispy import gloo
+
+c = app.Canvas(show=True, keys='interactive')
+
+
+ at c.connect
+def on_draw(event):
+    gloo.set_clear_color((0.2, 0.4, 0.6, 1.0))
+    gloo.clear()
+
+if __name__ == '__main__':
+    app.run()
diff --git a/examples/basics/gloo/start_shaders.py b/examples/basics/gloo/start_shaders.py
new file mode 100644
index 0000000..3e9b09a
--- /dev/null
+++ b/examples/basics/gloo/start_shaders.py
@@ -0,0 +1,38 @@
+from vispy import gloo
+from vispy import app
+import numpy as np
+
+VERT_SHADER = """
+#version 120
+attribute vec2 a_position;
+void main() {
+    gl_Position = vec4(a_position, 0.0, 1.0);
+    gl_PointSize = 10.0;
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+void main() {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.program['a_position'] = np.random.uniform(-0.5, 0.5, size=(2, 20))
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+        gloo.clear('white')
+        self.program.draw('points')
+
+c = Canvas()
+c.show()
+app.run()
diff --git a/examples/basics/plotting/mpl_plot.py b/examples/basics/plotting/mpl_plot.py
new file mode 100644
index 0000000..a608686
--- /dev/null
+++ b/examples/basics/plotting/mpl_plot.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Example demonstrating how to use vispy.pyplot, which uses mplexporter
+to convert matplotlib commands to vispy draw commands.
+
+Requires matplotlib.
+"""
+
+import numpy as np
+import sys
+
+import vispy.mpl_plot as plt
+from vispy.io import read_png, load_data_file
+
+n = 200
+freq = 10
+fs = 100.
+t = np.arange(n) / fs
+tone = np.sin(2*np.pi*freq*t)
+noise = np.random.RandomState(0).randn(n)
+signal = tone + noise
+magnitude = np.abs(np.fft.fft(signal))
+freqs = np.fft.fftfreq(n, 1. / fs)
+flim = n // 2
+
+# Signal
+fig = plt.figure()
+ax = plt.subplot(311)
+ax.imshow(read_png(load_data_file('pyplot/logo.png')))
+
+ax = plt.subplot(312)
+ax.plot(t, signal, 'k-')
+
+# Frequency content
+ax = plt.subplot(313)
+idx = np.argmax(magnitude[:flim])
+ax.text(freqs[idx], magnitude[idx], 'Max: %s Hz' % freqs[idx],
+        verticalalignment='top')
+ax.plot(freqs[:flim], magnitude[:flim], 'k-o')
+
+plt.draw()
+
+# NOTE: show() has currently been overwritten to convert to vispy format, so:
+# 1. It must be called to show the results, and
+# 2. Any plotting commands executed after this will not take effect.
+# We are working to remove this limitation.
+
+block = False if sys.flags.interactive else True
+plt.show(block)
diff --git a/examples/basics/plotting/vispy_plot.py b/examples/basics/plotting/vispy_plot.py
new file mode 100644
index 0000000..2c2e024
--- /dev/null
+++ b/examples/basics/plotting/vispy_plot.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+import vispy.plot as vplt
+
+plot_data = [1, 6, 2, 4, 3, 8, 4, 6, 5, 2]
+canvas1 = vplt.plot(plot_data)
+
+image_data = np.random.normal(size=(20, 20), loc=128, scale=60)
+canvas2 = vplt.image(image_data.astype(np.ubyte))
+
+
+# Start up the event loop if this is not an interactive prompt.
+import sys
+if sys.flags.interactive == 0:
+    canvas1.app.run()
diff --git a/examples/basics/scene/grid.py b/examples/basics/scene/grid.py
new file mode 100644
index 0000000..edabb0e
--- /dev/null
+++ b/examples/basics/scene/grid.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Test automatic layout of multiple viewboxes using Grid.
+"""
+import sys
+import numpy as np
+
+from vispy import scene, app
+
+canvas = scene.SceneCanvas(keys='interactive')
+canvas.size = 600, 600
+canvas.show()
+
+# This is the top-level widget that will hold three ViewBoxes, which will
+# be automatically resized whenever the grid is resized.
+grid = canvas.central_widget.add_grid()
+
+
+# Add 3 ViewBoxes to the grid
+b1 = grid.add_view(row=0, col=0, col_span=2)
+b1.border_color = (0.5, 0.5, 0.5, 1)
+b1.camera.rect = (-0.5, -5), (11, 10)
+b1.border = (1, 0, 0, 1)
+
+b2 = grid.add_view(row=1, col=0)
+b2.border_color = (0.5, 0.5, 0.5, 1)
+b2.camera.rect = (-10, -5), (15, 10)
+b2.border = (1, 0, 0, 1)
+
+b3 = grid.add_view(row=1, col=1)
+b3.border_color = (0.5, 0.5, 0.5, 1)
+b3.camera.rect = (-5, -5), (10, 10)
+b3.border = (1, 0, 0, 1)
+
+
+# Generate some random vertex data and a color gradient
+N = 10000
+pos = np.empty((N, 2), dtype=np.float32)
+pos[:, 0] = np.linspace(0, 10, N)
+pos[:, 1] = np.random.normal(size=N)
+pos[5000, 1] += 50
+
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+# Top grid cell shows plot data in a rectangular coordinate system.
+l1 = scene.visuals.Line(pos=pos, color=color, antialias=False, mode='gl')
+b1.add(l1)
+grid1 = scene.visuals.GridLines(parent=b1.scene)
+
+# Bottom-left grid cell shows the same data with log-transformed X
+e2 = scene.Entity(parent=b2.scene)
+e2.transform = scene.transforms.LogTransform(base=(2, 0, 0))
+l2 = scene.visuals.Line(pos=pos, color=color, antialias=False, parent=e2,
+                        mode='gl')
+grid2 = scene.visuals.GridLines(parent=e2)
+
+# Bottom-right grid cell shows the same data again, but with a much more
+# interesting transformation.
+e3 = scene.Entity(parent=b3.scene)
+affine = scene.transforms.AffineTransform()
+affine.scale((1, 0.1))
+affine.rotate(10, (0, 0, 1))
+affine.translate((0, 1))
+e3.transform = scene.transforms.ChainTransform([
+    scene.transforms.PolarTransform(),
+    affine])
+l3 = scene.visuals.Line(pos=pos, color=color, antialias=False, parent=e3,
+                        mode='gl')
+grid3 = scene.visuals.GridLines(scale=(np.pi/6., 1.0), parent=e3)
+
+if __name__ == '__main__' and sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/grid_large.py b/examples/basics/scene/grid_large.py
new file mode 100644
index 0000000..564d9ca
--- /dev/null
+++ b/examples/basics/scene/grid_large.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Test automatic layout of multiple viewboxes using Grid.
+"""
+
+from vispy import scene
+from vispy import app
+import numpy as np
+
+canvas = scene.SceneCanvas(keys='interactive')
+canvas.size = 600, 600
+canvas.show()
+
+grid = canvas.central_widget.add_grid()
+
+
+N = 10000
+lines = []
+for i in range(10):
+    lines.append([])
+    for j in range(10):
+        vb = grid.add_view(row=i, col=j)
+        vb.camera.rect = (0, -5), (100, 10)
+        vb.border = (1, 1, 1, 0.4)
+
+        pos = np.empty((N, 2), dtype=np.float32)
+        pos[:, 0] = np.linspace(0, 100, N)
+        pos[:, 1] = np.random.normal(size=N)
+        line = scene.visuals.Line(pos=pos, color=(1, 1, 1, 0.5), mode='gl')
+        vb.add(line)
+
+
+import sys
+if __name__ == '__main__' and sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/image.py b/examples/basics/scene/image.py
new file mode 100644
index 0000000..8e8c7b1
--- /dev/null
+++ b/examples/basics/scene/image.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Simple use of SceneCanvas to display an Image.
+"""
+from vispy import scene
+from vispy import app
+import numpy as np
+
+canvas = scene.SceneCanvas(keys='interactive')
+canvas.size = 800, 600
+canvas.show()
+
+# Set up a viewbox to display the image with interactive pan/zoom
+view = canvas.central_widget.add_view()
+
+# Create the image
+img_data = np.random.normal(size=(100, 100, 3), loc=128,
+                            scale=50).astype(np.ubyte)
+image = scene.visuals.Image(img_data, parent=view.scene)
+
+# Set the view bounds to show the entire image with some padding
+view.camera.rect = (-10, -10, image.size[0]+20, image.size[1]+20)
+
+import sys
+if __name__ == '__main__' and sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/isocurve.py b/examples/basics/scene/isocurve.py
new file mode 100644
index 0000000..ec55ea6
--- /dev/null
+++ b/examples/basics/scene/isocurve.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Simple use of SceneCanvas to display an Isocurve visual.
+"""
+from vispy import app, scene
+from vispy.util.filter import gaussian_filter
+import numpy as np
+
+canvas = scene.SceneCanvas(keys='interactive')
+canvas.size = 800, 600
+canvas.show()
+
+# Set up a viewbox to display the image with interactive pan/zoom
+view = canvas.central_widget.add_view()
+
+# Create the image
+img_data = np.empty((100, 100, 3), dtype=np.ubyte)
+noise = np.random.normal(size=(100, 100), loc=50, scale=150)
+noise = gaussian_filter(noise, (4, 4, 0))
+img_data[:] = noise[..., np.newaxis]
+image = scene.visuals.Image(img_data, parent=view.scene)
+
+# Create isocurve, make a child of the image to ensure the two are always
+# aligned.
+curve1 = scene.visuals.Isocurve(noise, level=60, color=(0, 0, 1, 1), 
+                                parent=view.scene)
+curve2 = scene.visuals.Isocurve(noise, level=50, color=(0, 0, 0.5, 1), 
+                                parent=view.scene)
+curve3 = scene.visuals.Isocurve(noise, level=40, color=(0, 0, 0.3, 1), 
+                                parent=view.scene)
+
+# Set the view bounds to show the entire image with some padding
+view.camera.rect = (-10, -10, image.size[0]+20, image.size[1]+20)
+
+
+import sys
+if __name__ == '__main__' and sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/isosurface.py b/examples/basics/scene/isosurface.py
new file mode 100644
index 0000000..36d52ef
--- /dev/null
+++ b/examples/basics/scene/isosurface.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+"""
+This example demonstrates the use of the Isosurface visual.
+"""
+
+import sys
+import numpy as np
+
+from vispy import app, scene
+
+# Create a canvas with a 3D viewport
+canvas = scene.SceneCanvas(keys='interactive')
+canvas.show()
+view = canvas.central_widget.add_view()
+view.set_camera('turntable', mode='perspective', up='z', distance=50)
+
+
+## Define a scalar field from which we will generate an isosurface
+def psi(i, j, k, offset=(25, 25, 50)):
+    x = i-offset[0]
+    y = j-offset[1]
+    z = k-offset[2]
+    th = np.arctan2(z, (x**2+y**2)**0.5)
+    r = (x**2 + y**2 + z**2)**0.5
+    a0 = 1
+    ps = ((1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * 
+          np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1))
+    
+    return ps
+
+print("Generating scalar field..")
+data = np.abs(np.fromfunction(psi, (50, 50, 100)))
+
+# Create isosurface visual
+surface = scene.visuals.Isosurface(data, level=data.max()/4., 
+                                   color=(0.5, 0.6, 1, 1), shading='smooth',
+                                   parent=view.scene)
+surface.transform = scene.transforms.STTransform(translate=(-25, -25, -50))
+
+# Add a 3D axis to keep us oriented
+axis = scene.visuals.XYZAxis(parent=view.scene)
+
+
+if sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/line.py b/examples/basics/scene/line.py
new file mode 100644
index 0000000..cf31694
--- /dev/null
+++ b/examples/basics/scene/line.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Simple demonstration of SceneCanvas containing a single line entity
+as its entire scenegraph.
+"""
+import sys
+import numpy as np
+
+from vispy import app, scene
+
+canvas = scene.SceneCanvas(size=(800, 600), show=True, keys='interactive')
+
+N = 1000
+pos = np.empty((N, 2), np.float32)
+pos[:, 0] = np.linspace(50., 750., N)
+#
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+lines = []
+
+print('Generating points...')
+for i in range(20):
+    pos = pos.copy()
+    pos[:, 1] = np.random.normal(scale=5, loc=(i+1)*30, size=N)
+    line = scene.visuals.Line(pos=pos, color=color, parent=canvas.scene)
+    lines.append(line)
+    line.transform = scene.transforms.STTransform()
+print('Done')
+
+
+def update(event):
+    for line in lines:
+        scale = [np.sin(np.pi * event.elapsed)+2,
+                 np.cos(np.pi * event.elapsed)+2]
+        line.transform.scale = scale
+
+timer = app.Timer('auto', connect=update, start=True)
+
+if sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/modular_shaders/editor.py b/examples/basics/scene/modular_shaders/editor.py
new file mode 100644
index 0000000..1c6a857
--- /dev/null
+++ b/examples/basics/scene/modular_shaders/editor.py
@@ -0,0 +1,189 @@
+#-------------------------------------------------------------------------
+# QScintilla editor
+#
+# Adapted from Eli Bendersky (eliben at gmail.com)
+# This code is in the public domain
+#
+# API: http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintilla.html
+#
+#-------------------------------------------------------------------------
+import sys
+import re
+from PyQt4.QtCore import *  # noqa
+from PyQt4.QtGui import *  # noqa
+
+
+try:
+    from PyQt4 import Qsci
+    from PyQt4.Qsci import QsciScintilla
+    HAVE_QSCI = True
+except ImportError:
+    HAVE_QSCI = False
+
+if not HAVE_QSCI:
+    # backup editor in case QScintilla is not available
+    class Editor(QPlainTextEdit):
+        def __init__(self, parent=None, language=None):
+            QPlainTextEdit.__init__(self, parent)
+
+        def setText(self, text):
+            self.setPlainText(text)
+
+        def text(self):
+            return str(self.toPlainText()).encode('UTF-8')
+
+        def __getattr__(self, name):
+            return lambda: None
+
+else:
+    class Editor(QsciScintilla):
+        ARROW_MARKER_NUM = 8
+
+        def __init__(self, parent=None, language='Python'):
+            super(Editor, self).__init__(parent)
+            self.setIndentationsUseTabs(False)
+            self.setIndentationWidth(4)
+
+            # Set the default font
+            font = QFont()
+            font.setFamily('DejaVu Sans Mono')
+            font.setFixedPitch(True)
+            font.setPointSize(10)
+            self.setFont(font)
+            self.setMarginsFont(font)
+            self.zoomIn()
+
+            # Margin 0 is used for line numbers
+            fontmetrics = QFontMetrics(font)
+            self.setMarginsFont(font)
+            self.setMarginWidth(0, fontmetrics.width("000") + 6)
+            self.setMarginLineNumbers(0, True)
+            self.setMarginsBackgroundColor(QColor("#cccccc"))
+
+            self._marker = None
+            # Clickable margin 1 for showing markers
+            #self.setMarginSensitivity(1, True)
+            #self.connect(self,
+            #    SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
+            #    self.on_margin_clicked)
+            self.markerDefine(QsciScintilla.RightArrow, self.ARROW_MARKER_NUM)
+            self.setMarkerBackgroundColor(QColor("#ee1111"),
+                                          self.ARROW_MARKER_NUM)
+
+            # Brace matching: enable for a brace immediately before or after
+            # the current position
+            #
+            self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
+
+            # Current line visible with special background color
+            self.setCaretLineVisible(True)
+            self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
+
+            # Set Python lexer
+            # Set style for Python comments (style number 1) to a fixed-width
+            # courier.
+            #
+            lexer = getattr(Qsci, 'QsciLexer' + language)()
+            lexer.setDefaultFont(font)
+            self.setLexer(lexer)
+            self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')
+
+            # Don't want to see the horizontal scrollbar at all
+            # Use raw message to Scintilla here (all messages are documented
+            # here: http://www.scintilla.org/ScintillaDoc.html)
+            self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
+
+            self.setWrapMode(QsciScintilla.WrapWord)
+
+            self.setEolMode(QsciScintilla.EolUnix)
+            # not too small
+            #self.setMinimumSize(600, 450)
+
+        def set_marker(self, line):
+            self.clear_marker()
+            self.markerAdd(line, self.ARROW_MARKER_NUM)
+            self._marker = line
+
+        def clear_marker(self):
+            if self._marker is not None:
+                self.markerDelete(self._marker, self.ARROW_MARKER_NUM)
+
+        #def on_margin_clicked(self, nmargin, nline, modifiers):
+            ## Toggle marker for the line the margin was clicked on
+            #if self.markersAtLine(nline) != 0:
+                #self.markerDelete(nline, self.ARROW_MARKER_NUM)
+            #else:
+                #self.markerAdd(nline, self.ARROW_MARKER_NUM)
+
+        def wheelEvent(self, ev):
+            # Use ctrl+wheel to zoom in/out
+            if Qt.ControlModifier & ev.modifiers():
+                if ev.delta() > 0:
+                    self.zoomIn()
+                else:
+                    self.zoomOut()
+            else:
+                return super(Editor, self).wheelEvent(ev)
+
+        def keyPressEvent(self, ev):
+            if int(Qt.ControlModifier & ev.modifiers()) > 0:
+                if ev.key() == Qt.Key_Slash:
+                    self.comment(True)
+                    return
+                elif ev.key() == Qt.Key_Question:
+                    self.comment(False)
+                    return
+                elif (ev.key() == Qt.Key_Z and
+                      Qt.ShiftModifier & ev.modifiers()):
+                    self.redo()
+                    return
+                elif ev.key() == Qt.Key_Q:
+                    sys.exit(0)
+            return super(Editor, self).keyPressEvent(ev)
+
+        def text(self):
+            return str(super(Editor, self).text()).encode('UTF-8')
+
+        def comment(self, comment=True):
+            sel = self.getSelection()[:]
+            text = self.text()
+            lines = text.split('\n')
+            if sel[0] == -1:
+                # toggle for just this line
+                row, col = self.getCursorPosition()
+                line = lines[row]
+                self.setSelection(row, 0, row, len(line))
+                if comment:
+                    line = '#' + line
+                else:
+                    line = line.replace("#", "", 1)
+                self.replaceSelectedText(line)
+                self.setCursorPosition(row, col+(1 if col > 0 else 0))
+            else:
+                block = lines[sel[0]:sel[2]]
+                # make sure all lines have #
+                new = []
+                if comment:
+                    for line in block:
+                        new.append('#' + line)
+                else:
+                    for line in block:
+                        if line.strip() == '':
+                            new.append(line)
+                            continue
+                        if re.match(r'\s*\#', line) is None:
+                            return
+                        new.append(line.replace('#', '', 1))
+                self.setSelection(sel[0], 0, sel[2], 0)
+                self.replaceSelectedText('\n'.join(new) + '\n')
+                #shift = 1 if comment else -1
+                self.setSelection(sel[0], max(0, sel[1]), sel[2], sel[3])
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+    editor = Editor()
+    editor.show()
+    editor.setText(open(sys.argv[0]).read())
+    editor.resize(800, 800)
+    app.exec_()
diff --git a/examples/basics/scene/modular_shaders/sandbox.py b/examples/basics/scene/modular_shaders/sandbox.py
new file mode 100644
index 0000000..b1c1144
--- /dev/null
+++ b/examples/basics/scene/modular_shaders/sandbox.py
@@ -0,0 +1,609 @@
+"""
+Sandbox for experimenting with vispy.scene.shaders
+
+"""
+from PyQt4 import QtCore
+from PyQt4.QtGui import *  # noqa
+import sys
+import traceback
+
+from editor import Editor, HAVE_QSCI
+
+
+presets = [
+    ('Introduction', '''
+"""
+             ------ Shader Composition Sandbox -------
+
+Instructions:
+
+1) Edit this code; it is immediately executed after every change. Exceptions
+   will be displayed on the right side.
+
+2) Assign strings to VERTEX and FRAGMENT variables (see below) and they will
+   appear in the windows to the right.
+
+3) Select presets from the list above to see a few examples.
+
+"""
+
+
+from vispy.scene.shaders import ModularProgram
+
+vertex_shader = "void main() {}"
+fragment_shader = "void main() {}"
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+'''),
+
+
+    ('Simple hook', '''
+"""
+In this example we define a 'hook' in the vertex shader: a function prototype
+with no definition. By leaving this function undefined, any new function
+definition may be concatenated to the shader.
+"""
+
+from vispy.scene.shaders import ModularProgram, Function
+
+# The hook is called 'input_position', and is used to provide the
+# value for gl_Position.
+vertex_shader = """
+vec4 input_position();
+
+void main() {
+    gl_Position = input_position();
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+# ModularProgram parses the shader code for function prototypes
+# and registers each as a hook.
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# Now we make a new function definition and attach it to the program.
+func = Function("""
+    vec4 input_position() {
+        return vec4(0,0,0,0);
+    }
+    """)
+
+program['input_position'] = func
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+'''),
+
+
+    ('Anonymous functions', '''
+"""
+Functions may optionally be defined with '$' in front of the function name.
+This indicates that the function is anonymous (has no name) and thus may be
+assigned any new name in the program.
+
+The major benefit to using anonymous functions is that the modular shader
+system is free to rename functions that would otherwise conflict with each
+other.
+
+In this example, an anonymous function is assigned to a hook. When it is
+compiled into the complete program, it is renamed to match the hook.
+"""
+
+from vispy.scene.shaders import ModularProgram, Function
+
+vertex_shader = """
+vec4 input_position();
+
+void main() {
+    gl_Position = input_position();
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# Now we make a new function definition and attach it to the program.
+# Note that this function is anonymous (name begins with '$') and does not
+# have the correct name to be attached to the input_position hook.
+func = Function("""
+    vec4 $my_function() {
+        return vec4(0,0,0,0);
+    }
+    """)
+
+program['input_position'] = func
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+'''),
+
+
+    ('Program variables', '''
+"""
+Many Functions need to define their own program variables
+(uniform/attribute/varying) in order to operate correctly. However, with many
+independent functions added to a ModularProgram, it is likely that two
+Functions might try to define variables of the same name.
+
+To solve this, Functions may use $anonymous_variables that will be assigned to
+a real program variable at compile time.
+
+In the next example, we will see how ModularProgram resolves name conflicts.
+"""
+
+from vispy.scene.shaders import ModularProgram, Function
+import numpy as np
+
+vertex_shader = """
+vec4 transform_position(vec4);
+
+attribute vec4 position_a;
+
+void main() {
+    gl_Position = transform_position(position_a);
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# Define a function to do a matrix transform.
+# The variable $matrix will be substituted with a uniquely-named program
+# variable when the function is compiled.
+func = Function("""
+    vec4 $matrix_transform(vec4 pos) {
+        return $matrix * pos;
+    }
+    """)
+
+# The definition for 'matrix' must indicate the variable type and data type.
+func['matrix'] = ('uniform', 'mat4', np.eye(4))
+
+
+program.set_hook('transform_position', func)
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+
+'''),
+
+
+    ('Resolving name conflicts', '''
+"""
+When anonymous functions and variables have conflicting names, the
+ModularProgram will generate unique names by appending _N to the end of the
+name.
+
+This example demonstrates dynamic naming of a program variable.
+"""
+
+from vispy.scene.shaders import ModularProgram, Function
+import numpy as np
+
+vertex_shader = """
+vec4 projection(vec4);
+vec4 modelview(vec4);
+
+attribute vec4 position_a;
+
+void main() {
+    gl_Position = projection(modelview(position_a));
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# Define two identical functions
+projection = Function("""
+    vec4 $matrix_transform(vec4 pos) {
+        return $matrix * pos;
+    }
+    """)
+projection['matrix'] = ('uniform', 'mat4', np.eye(4))
+
+modelview = Function("""
+    vec4 $matrix_transform(vec4 pos) {
+        return $matrix * pos;
+    }
+    """)
+modelview['matrix'] = ('uniform', 'mat4', np.eye(4))
+
+
+program.set_hook('projection', projection)
+program.set_hook('modelview', modelview)
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+
+'''),
+
+
+    ('Function chaining', '''
+"""
+Function chains are another essential component of shader composition,
+allowing a list of functions to be executed in order.
+"""
+
+from vispy.scene.shaders import ModularProgram, Function, FunctionChain
+
+# Added a new hook to allow any number of functions to be executed
+# after gl_Position is set.
+vertex_shader = """
+void vert_post_hook();
+
+attribute vec4 position_a;
+
+void main() {
+    gl_Position = position_a;
+    vert_post_hook();
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# Add a function to flatten the z-position of the vertex
+flatten = Function("""
+    void flatten_func() {
+        gl_Position.z = 0;
+    }
+    """)
+
+# Add another function that copies an attribute to a varying
+# for use in the fragment shader
+read_color_attr = Function("""
+    void $read_color_attr() {
+        $output = $input;
+    }
+    """)
+
+# ..and set two new program variables:
+# (note that no value is needed for varyings)
+read_color_attr['output'] = ('varying', 'vec4')
+read_color_attr['input'] = ('attribute', 'vec4', 'color_a')
+
+
+# Now create a chain that calls both functions in sequence
+post_chain = FunctionChain('vert_post_hook', [flatten, read_color_attr])
+
+program.set_hook('vert_post_hook', post_chain)
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+'''),
+
+
+    ('Function composition', '''
+"""
+Chains may also be used to generate a function composition where the return
+value of each function call supplies the input to the next argument.
+Thus, the original input is transformed in a series steps.
+
+This is most commonly used for passing vertex positions through a composition
+of transform functions.
+"""
+
+from vispy.scene.shaders import ModularProgram, Function, FunctionChain
+
+
+vertex_shader = """
+vec4 transform_chain(vec4);
+
+attribute vec4 position_a;
+
+void main() {
+    gl_Position = transform_chain(position_a);
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+flatten = Function("""
+    vec4 flatten_func(vec4 pos) {
+        pos.z = 0;
+        pos.w = 1;
+        return pos;
+    }
+    """)
+
+# Define a scaling function
+scale = Function("""
+    vec4 $scale_vertex(vec4 pos) {
+        return pos * vec4($scale, 1);
+    }
+    """)
+scale['scale'] = ('uniform', 'vec3', (2, 1, 1))
+
+# Assigning a list of both functions to a program hook will gemerate a
+# composition of functions:
+program['transform_chain'] = [flatten, scale]
+
+# Internally, this creates a FunctionChain:
+# transform = FunctionChain('transform_chain', [flatten, scale])
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+
+'''),
+
+
+    ('Fragment shaders', '''
+"""
+Although the prior examples focused on vertex shaders, these concepts
+apply equally well for fragment shaders.
+
+However: fragment shaders have one limitation that makes them very
+different--they lack attributes. In order to supply attribute data
+to a fragment shader, we will need to introduce some supporting code
+to the vertex shader.
+"""
+
+from vispy.scene.shaders import (ModularProgram, Function, FunctionChain)
+from vispy.gloo import VertexBuffer
+import numpy as np
+
+# we require a void hook in the vertex shader that can be used
+# to attach supporting code for the fragment shader.
+vertex_shader = """
+void vert_post_hook();
+
+attribute vec4 position_a
+
+void main() {
+    gl_Position = position_a;
+    vert_post_hook();
+}
+"""
+
+# add a hook to the fragment shader to allow arbitrary color input
+fragment_shader = """
+vec4 fragment_color();
+
+void main() {
+    gl_FragColor = fragment_color();
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# First, define a simple fragment color function and bind it to a varying
+# input:
+frag_func = Function("vec4 $frag_color_input() { return $f_input; }")
+frag_func['f_input'] = ('varying', 'vec4')
+
+# Attach to the program
+program['fragment_color'] = frag_func
+
+# Next, we need a vertex shader function that will supply input
+# to the varying.
+vert_func = Function("void $vert_color_input() { $v_output = $v_input; }")
+colors = VertexBuffer(np.array([[1,1,1,1]], dtype=np.float32))
+vert_func['v_input'] = ('attribute', 'vec4', colors)
+
+# to ensure both the vertex function output and the fragment function input
+# are attached to the same varying, we use the following syntax:
+vert_func['v_output'] = frag_func['f_input']
+
+# and attach this to the vertex shader
+program['vert_post_hook'] = vert_func
+
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+'''),
+
+
+    ('Sub-hooks', '''
+"""
+"""
+
+from vispy.scene.shaders import (ModularProgram, Function, FunctionChain)
+from vispy.gloo import VertexBuffer
+import numpy as np
+
+vertex_shader = """
+void vert_post_hook();
+
+void main() {
+    gl_Position = vec4(0,0,0,0);
+    vert_post_hook();
+}
+"""
+
+fragment_shader = """
+void main() {
+}
+"""
+
+program = ModularProgram(vertex_shader, fragment_shader)
+
+# Create a function that calls another function
+vert_func = Function("""
+void $vert_func() {
+    $some_other_function();
+}
+""")
+
+
+# Create the second function:
+other_func = Function("""
+void $other_func() {
+    gl_Position.w = 1;
+}
+""")
+
+# Assign other_func to the anonymous function call in vert_func:
+vert_func['some_other_function'] = other_func
+
+# The name assigned to other_func will be inserted in place of
+# the function call in vert_func
+
+program['vert_post_hook'] = vert_func
+
+# obligatory: these variables are used to fill the text fields on the right.
+program._compile()
+VERTEX = program.vert_code
+FRAGMENT = program.frag_code
+'''),
+
+
+]
+
+
+qsci_note = """
+#  [[ NOTE: Install PyQt.QsciScintilla for improved code editing ]]
+#  [[ (Debian packages: python-qscintilla2 or python3-pyqt4.qsci ]]
+
+"""
+if not HAVE_QSCI:
+    presets[0] = (presets[0][0], qsci_note + presets[0][1])
+
+
+app = QApplication([])
+
+win = QMainWindow()
+cw = QWidget()
+win.setCentralWidget(cw)
+layout = QGridLayout()
+cw.setLayout(layout)
+
+editor = Editor(language='Python')
+vertex = Editor(language='CPP')
+fragment = Editor(language='CPP')
+for i in range(3):
+    editor.zoomOut()
+    vertex.zoomOut()
+    fragment.zoomOut()
+
+hsplit = QSplitter(QtCore.Qt.Horizontal)
+vsplit = QSplitter(QtCore.Qt.Vertical)
+
+layout.addWidget(hsplit)
+hsplit.addWidget(editor)
+hsplit.addWidget(vsplit)
+vsplit.addWidget(vertex)
+vsplit.addWidget(fragment)
+
+menubar = win.menuBar()
+
+last_loaded = -1
+
+
+def load_example(name):
+    global last_loaded
+    if isinstance(name, int):
+        code = presets[name][1]
+        editor.setText(code)
+        last_loaded = name
+    else:
+        for i, preset in enumerate(presets):
+            n, code = preset
+            if n == name:
+                editor.setText(code)
+                last_loaded = i
+                return
+
+
+def load_next():
+    global last_loaded
+    try:
+        load_example(last_loaded+1)
+    except IndexError:
+        pass
+
+
+def mk_load_callback(name):
+    return lambda: load_example(name)
+
+example_menu = menubar.addMenu('Load example..')
+for i, preset in enumerate(presets):
+    name = preset[0]
+    action = example_menu.addAction("%d. %s" % (i, name),
+                                    mk_load_callback(name))
+
+next_action = menubar.addAction("Next example", load_next)
+
+win.show()
+win.resize(1800, 1100)
+hsplit.setSizes([900, 900])
+
+load_example(0)
+
+
+def update():
+    code = editor.text()
+    local = {}
+    glob = {}
+    try:
+        exec(code, local, glob)
+        vert = glob['VERTEX']
+        frag = glob['FRAGMENT']
+        editor.clear_marker()
+    except:
+        vert = traceback.format_exc()
+        frag = ""
+        tb = sys.exc_info()[2]
+        while tb is not None:
+            #print(tb.tb_lineno, tb.tb_frame.f_code.co_filename)
+            if tb.tb_frame.f_code.co_filename == '<string>':
+                editor.set_marker(tb.tb_lineno-1)
+            tb = tb.tb_next
+
+    vertex.setText(vert)
+    fragment.setText(frag)
+
+editor.textChanged.connect(update)
+update()
+
+app.exec_()
diff --git a/examples/basics/scene/nested_viewbox.py b/examples/basics/scene/nested_viewbox.py
new file mode 100644
index 0000000..3b183bd
--- /dev/null
+++ b/examples/basics/scene/nested_viewbox.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# vispy: gallery 2
+"""
+Simple test of nested viewboxes, demonstrating the three methods that
+can be used by a viewbox to provide clipping.
+
+In the root scene are two viewboxes: the left viewbox uses the 'viewport'
+clipping method and a PanZoomCamera, whereas the right viewbox uses the 'fbo'
+clipping method and a base Camera (null transform).
+
+Each of these viewboxes contains again two viewboxes, with the same
+differences. In this way we test embedding each type of viewbox inside each
+type.
+
+This is what it should look like:
+
+The plot line has a "marker" region on the left side that points in the +y
+direction. In pixel coordinates, this is normally expected to point downward
+(because the pixel y-axis points down). However, the default behavior for
+PanZoomCamera is to reverse its internal y-axis relative to its parent.
+
+    +-----------------+-----------------+
+    | | vb1 uses      | | vb2 uses      |
+    | | PanZoomCamera | | base Camera   |
+    | | (+y upward)   | | (+y downward) |
+    +=================+=================+
+    |                 |                 |
+    |    +y upward    |    +y upward    |
+    |                 |                 |
+    +-----------------+-----------------+
+    |                 |                 |
+    |   +y downward   |   +y downward   |
+    |                 |                 |
+    +-----------------+-----------------+
+"""
+
+import numpy as np
+
+from vispy import app
+from vispy import scene
+
+#gloo.gl.use('desktop debug')
+
+# <<< Change method here
+# With the None method you can see the absence of clipping.
+# With the 'fbo' method you can see the texture interpolation (induced by
+# a delibirate mismatch in screen and textue resolution)
+# Try different combinarions, like a viewport in an fbo
+CLIP_METHOD1 = 'viewport'  # none, viewport, fbo (fragment to come)
+CLIP_METHOD2 = 'fbo'
+
+
+# Create lines for use in ndc and pixel coordinates
+N = 1000
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+pos = np.empty((N, 2), np.float32)
+pos[:, 0] = np.linspace(0., 1., N)
+pos[:, 1] = np.random.normal(loc=0.5, scale=0.03, size=N)
+pos[N/2:N/2+20, 1] = 0.9  # So we can see which side is up
+
+
+# Create canvas
+canvas = scene.SceneCanvas(size=(800, 600), show=True, keys='interactive')
+
+#
+# Create viewboxes on left ...
+#
+
+w, h = canvas.size
+w2 = w / 2.
+h2 = h / 2.
+
+# left (+y up)
+vb1 = scene.widgets.ViewBox(parent=canvas.scene, name='vb1', 
+                            margin=2, border_color='red')
+vb1.pos = 0, 0
+vb1.size = w2, h
+vb1.camera.rect = (0, 0, 1, 1)
+vb1.camera.interactive = False
+
+# bottom-left (+y down)
+vb11 = scene.widgets.ViewBox(parent=vb1.scene, name='vb11', 
+                             margin=0.02, border_color='green')
+vb11.pos = 0, 0
+vb11.size = 1, 0.5
+vb11.camera.rect = (0, 0, 1, 1)
+line11 = scene.visuals.Line(pos=pos, color=color, mode='gl', parent=vb11.scene)
+
+# top-left (+y up)
+vb12 = scene.widgets.ViewBox(parent=vb1.scene, name='vb12', 
+                             margin=0.02, border_color='blue')
+vb12.pos = 0, 0.5
+vb12.size = 1, 0.5
+vb12.set_camera(None)  # use parent cs
+# vb12 does not apply any scaling, so we do that manually here to match vb11
+line12 = scene.visuals.Line(pos=pos * [[1.0, 0.5]], color=color, mode='gl', 
+                            parent=vb12.scene)
+
+
+#
+# Create viewboxes on right ...
+#
+
+# right (+y down)
+vb2 = scene.widgets.ViewBox(parent=canvas.scene, name='vb2', 
+                            margin=2, border_color='yellow')
+vb2.pos = w2, 0
+vb2.size = w2, h
+vb2.set_camera(None)
+vb2.camera.interactive = False
+
+# top-right (+y up)
+vb21 = scene.widgets.ViewBox(parent=vb2.scene, name='vb21', 
+                             margin=10, border_color='purple')
+vb21.pos = 0, 0
+vb21.size = w2, h2
+vb21.camera.rect = (0, 0, 1, 1)
+line21 = scene.visuals.Line(pos=pos, color=color, mode='gl', parent=vb21.scene)
+
+# bottom-right (+y down)
+vb22 = scene.widgets.ViewBox(parent=vb2.scene, name='vb22', 
+                             margin=10, border_color='teal')
+vb22.pos = 0, h2
+vb22.size = w2, h2
+vb22.set_camera(None)  # use parent cs
+# vb22 does not apply any scaling, so we do that manually here to match vb21
+line22 = scene.visuals.Line(pos=pos * [[w2, h2]], color=color, mode='gl', 
+                            parent=vb22.scene)
+
+
+# Set preferred clipping methods
+for vb in [vb1, vb11, vb21]:
+    vb.preferred_clip_method = CLIP_METHOD1
+for vb in [vb2, vb12, vb22]:
+    vb.preferred_clip_method = CLIP_METHOD2
+
+
+if __name__ == '__main__':
+    app.run()
diff --git a/examples/basics/scene/surface_plot.py b/examples/basics/scene/surface_plot.py
new file mode 100644
index 0000000..e0c66ca
--- /dev/null
+++ b/examples/basics/scene/surface_plot.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+This example demonstrates the use of the SurfacePlot visual.
+"""
+
+import sys
+import numpy as np
+
+from vispy import app, scene
+from vispy.util.filter import gaussian_filter
+
+
+canvas = scene.SceneCanvas(keys='interactive')
+canvas.show()
+view = canvas.central_widget.add_view()
+view.set_camera('turntable', mode='perspective', up='z', distance=2)
+
+## Simple surface plot example
+## x, y values are not specified, so assumed to be 0:50
+z = gaussian_filter(np.random.normal(size=(50, 50)), (1, 1)) * 10
+p1 = scene.visuals.SurfacePlot(z=z, color=(0.5, 0.5, 1, 1), shading='smooth')
+p1.transform = scene.transforms.AffineTransform()
+p1.transform.scale([1/49., 1/49., 0.02])
+p1.transform.translate([-0.5, -0.5, 0])
+
+view.add(p1)
+
+# Add a 3D axis to keep us oriented
+axis = scene.visuals.XYZAxis(parent=view.scene)
+
+if sys.flags.interactive == 0:
+    app.run()
diff --git a/examples/basics/scene/text.py b/examples/basics/scene/text.py
new file mode 100644
index 0000000..cb0de27
--- /dev/null
+++ b/examples/basics/scene/text.py
@@ -0,0 +1,43 @@
+# !/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Demonstrate the use of text in the root scene and a viewbox. Note
+how the point size is independent of scaling of viewbox and canvas.
+"""
+
+import numpy as np
+import vispy
+from vispy import scene
+from vispy.scene.visuals import Text
+
+# Create canvas with a viewbox at the lower half
+canvas = scene.SceneCanvas(keys='interactive')
+vb = scene.widgets.ViewBox(parent=canvas.scene, border_color='b')
+vb.camera.rect = 0, 0, 1, 1
+
+
+ at canvas.events.resize.connect
+def resize(event=None):
+    vb.pos = 1, canvas.size[1] // 2 - 1
+    vb.size = canvas.size[0] - 2, canvas.size[1] // 2 - 2
+
+t1 = Text('Text in root scene (24 pt)', parent=canvas.scene, color='red')
+t1.font_size = 24
+t1.pos = canvas.size[0] // 2, canvas.size[1] // 3
+
+t2 = Text('Text in viewbox (18 pt)', parent=vb.scene, color='green',
+          rotation=30)
+t2.font_size = 18
+t2.pos = 0.5, 0.3
+
+# Add a line so you can see translate/scale of camera
+N = 1000
+linedata = np.empty((N, 2), np.float32)
+linedata[:, 0] = np.linspace(0, 1, N)
+linedata[:, 1] = np.random.uniform(0.5, 0.1, (N,))
+vispy.scene.visuals.Line(pos=linedata, color='#f006', mode='gl', 
+                         parent=vb.scene)
+
+canvas.show()
+canvas.app.run()
diff --git a/examples/basics/scene/viewbox.py b/examples/basics/scene/viewbox.py
new file mode 100644
index 0000000..f692382
--- /dev/null
+++ b/examples/basics/scene/viewbox.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Demonstrate ViewBox using various clipping methods.
+
+Two boxes are manually positioned on the canvas; they are not updated
+when the canvas resizes.
+"""
+import sys
+import numpy as np
+
+from vispy import app
+from vispy import scene
+
+
+# Create canvas
+canvas = scene.SceneCanvas(size=(800, 600), show=True, keys='interactive')
+
+grid = canvas.central_widget.add_grid()
+
+# Create two ViewBoxes, place side-by-side
+# First ViewBox uses a 2D pan/zoom camera
+vb1 = scene.widgets.ViewBox(name='vb1', border_color='yellow', parent=grid)
+vb1.clip_method = 'fbo'
+vb1.camera.rect = (-1.2, -2, 2.4, 4)
+
+# Second ViewBox uses a 3D orthographic camera
+vb2 = scene.widgets.ViewBox(name='vb2', border_color='blue', parent=grid)
+vb2.parent = canvas.scene
+vb2.clip_method = 'viewport'
+vb2.set_camera('turntable', mode='ortho', elevation=30, azimuth=30, up='y')
+#vb2.set_camera('turntable', mode='perspective',
+#               distance=10, elevation=0, azimuth=0)
+
+
+# Move these when the canvas changes size
+ at canvas.events.resize.connect
+def resize(event=None):
+    vb1.pos = 20, 20
+    vb1.size = canvas.size[0]/2. - 40, canvas.size[1] - 40
+    vb2.pos = canvas.size[0]/2. + 20, 20
+    vb2.size = canvas.size[0]/2. - 40, canvas.size[1] - 40
+
+resize()
+
+
+#
+# Now add visuals to the viewboxes.
+#
+
+# First a plot line:
+N = 1000
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+pos = np.empty((N, 2), np.float32)
+pos[:, 0] = np.linspace(-1., 1., N)
+pos[:, 1] = np.random.normal(0.0, 0.5, size=N)
+pos[:20, 1] = -0.5  # So we can see which side is down
+
+# make a single plot line and display in both viewboxes
+line1 = scene.visuals.Line(pos=pos.copy(), color=color, mode='gl',
+                           antialias=False, name='line1', parent=vb1.scene)
+line1.add_parent(vb1.scene)
+line1.add_parent(vb2.scene)
+
+
+# And some squares:
+box = np.array([[0, 0, 0],
+                [0, 1, 0],
+                [1, 1, 0],
+                [1, 0, 0],
+                [0, 0, 0]], dtype=np.float32)
+z = np.array([[0, 0, 1]], dtype=np.float32)
+
+# First two boxes are added to both views
+box1 = scene.visuals.Line(pos=box, color=(0.7, 0, 0, 1), mode='gl',
+                          name='unit box', parent=vb1.scene)
+box1.add_parent(vb2.scene)
+
+box2 = scene.visuals.Line(pos=(box * 2 - 1),  color=(0, 0.7, 0, 1), mode='gl',
+                          name='nd box', parent=vb1.scene)
+box2.add_parent(vb2.scene)
+
+# These boxes are only added to the 3D view.
+box3 = scene.visuals.Line(pos=box + z, color=(1, 0, 0, 1), mode='gl',
+                          name='unit box', parent=vb2.scene)
+box4 = scene.visuals.Line(pos=((box + z) * 2 - 1), color=(0, 1, 0, 1),
+                          mode='gl', name='nd box', parent=vb2.scene)
+
+
+if __name__ == '__main__' and sys.flags.interactive == 0:
+    print(canvas.scene.describe_tree(with_transform=True))
+    app.run()
diff --git a/examples/basics/visuals/custom_visual.py b/examples/basics/visuals/custom_visual.py
new file mode 100644
index 0000000..09c1d20
--- /dev/null
+++ b/examples/basics/visuals/custom_visual.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+from __future__ import division
+import numpy as np
+from math import exp
+
+from vispy import app
+from vispy import gloo
+from vispy.scene.shaders import ModularProgram
+from vispy.scene.visuals import Visual
+from vispy.scene.transforms import STTransform, LogTransform
+
+
+class PanZoomTransform(STTransform):
+    def move(self, dx):
+        """I call this when I want to translate."""
+        dx, dy = dx
+        self.translate = (self.translate[0] + dx/self.scale[0],
+                          self.translate[1] + dy/self.scale[1])
+
+    def zoom(self, dx, center=(0., 0.)):
+        """I call this when I want to zoom."""
+        dx, dy = dx
+        scale = (self.scale[0] * exp(2.5*dx),
+                 self.scale[1] * exp(2.5*dy))
+        tr = self.translate
+        self.translate = (tr[0] - center[0] * (1./self.scale[0] - 1./scale[0]),
+                          tr[1] + center[1] * (1./self.scale[1] - 1./scale[1]))
+        self.scale = scale
+
+
+class MarkerVisual(Visual):
+    # My full vertex shader, with just a `transform` hook.
+    VERTEX_SHADER = """
+        #version 120
+        
+        attribute vec2 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 = $transform(vec4(a_position,0,1));
+            
+            gl_PointSize = 2.0*(v_radius + v_linewidth + 1.5*v_antialias);
+        }
+    """
+
+    FRAGMENT_SHADER = """
+        #version 120
+        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.0*(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);
+            }
+        }
+    """
+    
+    def __init__(self, pos=None, color=None, size=None):
+        self._program = ModularProgram(self.VERTEX_SHADER, 
+                                       self.FRAGMENT_SHADER)
+        self.set_data(pos=pos, color=color, size=size)
+        
+    def set_options(self):
+        """Special function that is used to set the options. Automatically
+        called at initialization."""
+        gloo.set_state(clear_color=(1, 1, 1, 1), blend=True, 
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def set_data(self, pos=None, color=None, size=None):
+        """I'm not required to use this function. We could also have a system
+        of trait attributes, such that a user doing
+        `visual.position = myndarray` results in an automatic update of the 
+        buffer. Here I just set the buffers manually."""
+        self._pos = pos
+        self._color = color
+        self._size = size
+        
+    def draw(self):
+        # attributes / uniforms are not available until program is built        
+        self._program.prepare()  # Force ModularProgram to set shaders
+        self._program['a_position'] = gloo.VertexBuffer(self._pos)
+        self._program['a_color'] = gloo.VertexBuffer(self._color)
+        self._program['a_size'] = gloo.VertexBuffer(self._size)
+        self._program.draw('points')
+    
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+
+        n = 10000
+        pos = 0.25 * np.random.randn(n, 2).astype(np.float32)
+        color = np.random.uniform(0, 1, (n, 3)).astype(np.float32)
+        size = np.random.uniform(2, 12, (n, 1)).astype(np.float32)
+
+        self.points = MarkerVisual(pos=pos, color=color, size=size)
+        
+        # This is just an instance I choose to use, nothing required in the
+        # Visual API here.
+        self.panzoom = PanZoomTransform()
+        
+        # Here, I set the `transform` hook to my PAN_ZOOM function.
+        # In addition, I provide a component instance.
+        # Vispy knows that every $variable in the Function is bound to
+        # component.$variable. Here, since pan and zoom are tuples,
+        # Vispy understands that it has to create two uniforms (u_$variable 
+        # for example).
+        tr = self.panzoom * LogTransform(base=(0, 2, 0))
+        self.points._program.vert['transform'] = tr.shader_map()
+    
+    def on_initialize(self, even):
+        gloo.set_state(blend=True,
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+    
+    def _normalize(self, xy):
+        x, y = xy
+        w, h = float(self.width), float(self.height)
+        return x/(w/2.)-1., y/(h/2.)-1.
+        
+    def on_mouse_move(self, event):
+        if event.is_dragging:
+            x0, y0 = event.press_event.pos
+            x1, y1 = event.last_event.pos
+            x, y = event.pos
+            dxy = ((x - x1) / self.size[0] * 2, -(y - y1) / self.size[1] * 2)
+            button = event.press_event.button
+            
+            # This just updates my private PanZoom instance. Nothing magic
+            # happens.
+            if button == 1:
+                self.panzoom.move(dxy)
+            elif button == 2:
+                self.panzoom.zoom(dxy)
+                
+            # The magic happens here. self.on_draw() is called, so 
+            # self.points.draw() is called. The two variables in the transform
+            # hook are bound to self.panzoom.pan and self.panzoom.zoom, so
+            # Vispy will automatically fetch those two values and update
+            # the two corresponding uniforms.
+            self.update()
+            
+            # Force transform to update its shader. 
+            # (this should not be necessary)
+            self.panzoom.shader_map()
+        
+    def on_resize(self, event):
+        self.width, self.height = event.size
+        gloo.set_viewport(0, 0, self.width, self.height)
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.points.draw()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/basics/visuals/image_transforms.py b/examples/basics/visuals/image_transforms.py
new file mode 100644
index 0000000..c187580
--- /dev/null
+++ b/examples/basics/visuals/image_transforms.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# vispy: gallery 2
+
+"""
+Simple demonstration of ImageVisual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene import visuals
+from vispy.scene.transforms import (AffineTransform, STTransform, arg_to_array,
+                                    LogTransform, PolarTransform, 
+                                    BaseTransform)
+
+image = np.random.normal(size=(100, 100, 3))
+image[20:80, 20:80] += 3.
+image[50] += 3.
+image[:, 50] += 3.
+
+image = ((image-image.min()) *
+         (253. / (image.max()-image.min()))).astype(np.ubyte)
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.images = [visuals.Image(image, method='impostor')
+                       for i in range(4)]
+        self.images[0].transform = (STTransform(scale=(30, 30),
+                                                translate=(600, 600)) * 
+                                    SineTransform() *
+                                    STTransform(scale=(0.1, 0.1),
+                                                translate=(-5, -5)))
+
+        tr = AffineTransform()
+        tr.rotate(30, (0, 0, 1))
+        tr.scale((3, 3))
+        self.images[1].transform = (STTransform(translate=(200, 600)) *
+                                    tr *
+                                    STTransform(translate=(-50, -50)))
+
+        self.images[2].transform = (STTransform(scale=(3, -150),
+                                                translate=(200, 100)) *
+                                    LogTransform((0, 2, 0)) *
+                                    STTransform(scale=(1, -0.01),
+                                                translate=(-50, 1.3)))
+
+        self.images[3].transform = (STTransform(scale=(400, 400),
+                                                translate=(600, 300)) *
+                                    PolarTransform() *
+                                    STTransform(scale=(np.pi/200, 0.005),
+                                                translate=(-3*np.pi/4., 0.1)))
+
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+
+    def on_draw(self, ev):
+        gloo.clear(color='black', depth=True)
+        self.push_viewport((0, 0) + self.size)
+        for img in self.images:
+            self.draw_visual(img)
+
+
+# A simple custom Transform
+class SineTransform(BaseTransform):
+    """
+    Add sine wave to y-value for wavy effect.
+    """
+    glsl_map = """
+        vec4 sineTransform(vec4 pos) {
+            return vec4(pos.x, pos.y + sin(pos.x), pos.z, 1);
+        }"""
+
+    glsl_imap = """
+        vec4 sineTransform(vec4 pos) {
+            return vec4(pos.x, pos.y - sin(pos.x), pos.z, 1);
+        }"""
+
+    Linear = False
+
+    @arg_to_array
+    def map(self, coords):
+        ret = coords.copy()
+        ret[..., 1] += np.sin(ret[..., 0])
+        return ret
+
+    @arg_to_array
+    def imap(self, coords):
+        ret = coords.copy()
+        ret[..., 1] -= np.sin(ret[..., 0])
+        return ret
+
+    def inverse(self):
+        return InvSineTransform()
+
+
+class InvSineTransform(BaseTransform):
+    glsl_map = SineTransform.glsl_imap
+    glsl_imap = SineTransform.glsl_map
+
+    Linear = False
+
+    map = SineTransform.imap
+    imap = SineTransform.map
+
+    def inverse(self):
+        return SineTransform()
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/image_visual.py b/examples/basics/visuals/image_visual.py
new file mode 100644
index 0000000..e3f2afa
--- /dev/null
+++ b/examples/basics/visuals/image_visual.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of ImageVisual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene import visuals
+from vispy.scene.transforms import STTransform
+
+image = np.random.normal(size=(100, 100, 3), loc=128,
+                         scale=50).astype(np.ubyte)
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.image = visuals.Image(image, method='subdivide')
+        self.image.transform = STTransform(scale=(7, 7), translate=(50, 50))
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+
+    def on_draw(self, ev):
+        gloo.clear(color='black', depth=True)
+        self.push_viewport((0, 0) + self.size)
+        self.draw_visual(self.image)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/line.py b/examples/basics/visuals/line.py
new file mode 100644
index 0000000..c822d2f
--- /dev/null
+++ b/examples/basics/visuals/line.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Demonstration of various features of Line visual.
+"""
+import sys
+import numpy as np
+
+import vispy.app
+from vispy.scene import visuals
+from vispy.scene.transforms import STTransform
+
+# vertex positions of data to draw
+N = 200
+pos = np.zeros((N, 2), dtype=np.float32)
+pos[:, 0] = np.linspace(10, 390, N)
+pos[:, 1] = np.random.normal(size=N, scale=20, loc=0)
+
+# color array
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+# connection array
+connect = np.empty((N-1, 2), np.int32)
+connect[:, 0] = np.arange(N-1)
+connect[:, 1] = connect[:, 0] + 1
+connect[N/2, 1] = N/2  # put a break in the middle
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive',
+                                         size=(800, 800), show=True)
+        # Create several visuals demonstrating different features of Line
+        self.lines = [
+            # agg-mode lines:
+            visuals.Line(pos=pos, color=color, mode='agg'),  # per-vertex color
+            visuals.Line(pos=pos, color=(0, 0.5, 0.3, 1), mode='agg'),  # solid
+            visuals.Line(pos=pos, color=color, width=5, mode='agg'),  # wide
+            # GL-mode lines:
+            visuals.Line(pos=pos, color=color, mode='gl'),
+            visuals.Line(pos=pos, color=(0, 0.5, 0.3, 1), mode='gl'),
+            visuals.Line(pos=pos, color=color, width=5, mode='gl'),
+            # GL-mode: "connect" not available in AGG mode yet
+            visuals.Line(pos=pos, color=(0, 0.5, 0.3, 1), connect='segments',
+                         mode='gl'),  # only connect alternate vert pairs
+            visuals.Line(pos=pos, color=(0, 0.5, 0.3, 1), connect=connect,
+                         mode='gl'),  # connect specific pairs
+        ]
+        counts = [0, 0]
+        for i, line in enumerate(self.lines):
+            # arrange lines in a grid
+            tidx = (line.mode == 'agg')
+            x = 400 * tidx
+            y = 140 * (counts[tidx] + 1)
+            counts[tidx] += 1
+            line.transform = STTransform(translate=[x, y])
+            # redraw the canvas if any visuals request an update
+            line.events.update.connect(lambda evt: self.update())
+            line.parent = self.central_widget
+        self.texts = [visuals.Text('GL', bold=True, font_size=24, color='w',
+                                   pos=(200, 40), parent=self.central_widget),
+                      visuals.Text('Agg', bold=True, font_size=24, color='w',
+                                   pos=(600, 40), parent=self.central_widget)]
+
+
+if __name__ == '__main__':
+    win = Canvas()
+
+    def update(ev):
+        pos[:, 1] = np.random.normal(size=N, scale=30, loc=0)
+        win.lines[0].set_data(pos)
+        win.lines[3].set_data(pos)
+
+    timer = vispy.app.Timer()
+    timer.connect(update)
+    timer.start(0)
+
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/line_plot.py b/examples/basics/visuals/line_plot.py
new file mode 100644
index 0000000..91dff9e
--- /dev/null
+++ b/examples/basics/visuals/line_plot.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of LinePlot visual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy.scene import visuals
+
+# vertex positions of data to draw
+N = 20
+pos = np.zeros((N, 2), dtype=np.float32)
+pos[:, 0] = np.linspace(10, 790, N)
+pos[:, 1] = np.random.normal(size=N, scale=100, loc=400)
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.line = visuals.LinePlot(pos, color='w', edge_color='w',
+                                     face_color=(0.2, 0.2, 1))
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive',
+                                         size=(800, 800), show=True)
+        self.line.parent = self.scene
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/line_transform.py b/examples/basics/visuals/line_transform.py
new file mode 100644
index 0000000..90fbe43
--- /dev/null
+++ b/examples/basics/visuals/line_transform.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 1
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Demonstration of Line visual with arbitrary transforms.
+
+Several Line visuals are displayed that all have the same vertex position
+information, but different transformations.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene import visuals
+from vispy.scene.transforms import (STTransform, LogTransform,
+                                    AffineTransform, PolarTransform)
+
+import vispy.util
+vispy.util.use_log_level('debug')
+
+# vertex positions of data to draw
+N = 200
+pos = np.zeros((N, 2), dtype=np.float32)
+pos[:, 0] = np.linspace(-350, 350, N)
+pos[:, 1] = np.random.normal(size=N, scale=50, loc=0)
+
+# One array of colors
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+
+        # Define several Line visuals that use the same position data
+        # but have different colors and transformations
+        colors = [color, (1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1),
+                  (1, 1, 0, 1), (1, 1, 1, 1)]
+
+        self.lines = [visuals.Line(pos=pos, color=colors[i])
+                      for i in range(6)]
+
+        center = STTransform(translate=(400, 400))
+
+        self.lines[0].transform = center
+
+        self.lines[1].transform = (center * 
+                                   STTransform(scale=(1, 0.1, 1)))
+
+        self.lines[2].transform = (center * 
+                                   STTransform(translate=(200, 200, 0)) *
+                                   STTransform(scale=(0.3, 0.5, 1)))
+
+        self.lines[3].transform = (center * 
+                                   STTransform(translate=(-200, -200, 0),
+                                               scale=(200, 1)) *
+                                   LogTransform(base=(10, 0, 0)) *
+                                   STTransform(translate=(1, 0, 0)))
+
+        self.lines[4].transform = AffineTransform()
+        self.lines[4].transform.rotate(45, (0, 0, 1))
+        self.lines[4].transform.scale((0.3, 0.3, 1))
+        self.lines[4].transform.translate((200, 200, 0))
+
+        self.lines[5].transform = (STTransform(translate=(200, 600, 0),
+                                               scale=(5, 5)) *
+                                   PolarTransform() *
+                                   LogTransform(base=(2, 0, 0)) *
+                                   STTransform(scale=(0.01, 0.1),
+                                               translate=(4, 20)))
+
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+
+    def on_draw(self, ev):
+        gloo.set_clear_color('black')
+        gloo.clear(color=True, depth=True)
+        gloo.set_viewport(0, 0, *self.size)
+        for line in self.lines:
+            self.draw_visual(line)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/line_update.py b/examples/basics/visuals/line_update.py
new file mode 100644
index 0000000..6112561
--- /dev/null
+++ b/examples/basics/visuals/line_update.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Demonstration of animated Line visual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy.scene import visuals
+
+# vertex positions of data to draw
+N = 200
+pos = np.zeros((N, 2), dtype=np.float32)
+pos[:, 0] = np.linspace(50., 750., N)
+pos[:, 1] = np.random.normal(size=N, scale=100, loc=400)
+
+# color array
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive',
+                                         size=(800, 800), show=True)
+        self.line = visuals.Line(pos, color, parent=self.scene)
+        self.line.events.update.connect(lambda evt: self.update)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+
+    def update(ev):
+        pos[:, 1] = np.random.normal(size=N, scale=100, loc=400)
+        win.line.set_data(pos=pos)
+
+    timer = vispy.app.Timer()
+    timer.connect(update)
+    timer.start(0)
+
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/markers.py b/examples/basics/visuals/markers.py
new file mode 100644
index 0000000..e67c862
--- /dev/null
+++ b/examples/basics/visuals/markers.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+""" Display markers at different sizes and line thicknessess.
+"""
+
+import numpy as np
+
+from vispy import app, gloo
+from vispy.scene.visuals import Markers, marker_types
+from vispy.scene.transforms import STTransform
+
+n = 540
+pos = np.zeros((n, 2))
+radius, theta, dtheta = 1.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
+    pos[i] = x, y
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive', size=(512, 512 + 2*32),
+                            title="Marker demo [press space to change marker]")
+        self.index = 0
+        self.scale = 1.
+        self.markers = Markers()
+        self.markers.set_data(pos)
+        self.markers.set_style(marker_types[self.index])
+
+    def on_initialize(self, event):
+        # We need to give a transform to our visual
+        self.transform = STTransform()
+        self.markers._program.vert['transform'] = self.transform.shader_map()
+        self.apply_zoom()
+
+    def on_draw(self, event):
+        gloo.clear(color='white')
+        self.markers.draw()
+
+    def on_mouse_wheel(self, event):
+        """Use the mouse wheel to zoom."""
+        self.scale *= 1.25 if event.delta[1] > 0 else 0.8
+        self.scale = max(min(self.scale, 1e2), 1e-2)
+        self.apply_zoom()
+
+    def on_resize(self, event):
+        self.apply_zoom()
+
+    def apply_zoom(self):
+        gloo.set_viewport(0, 0, *self.size)
+        self.transform.scale = (2 * self.scale / self.size[0],
+                                2 * self.scale / self.size[1], 1.)
+        self.transform.translate = [-1, -1]
+        self.update()
+
+    def on_key_press(self, event):
+        if event.text == ' ':
+            self.index = (self.index + 1) % (len(marker_types))
+            self.markers.set_style(marker_types[self.index])
+            self.update()
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/basics/visuals/mesh.py b/examples/basics/visuals/mesh.py
new file mode 100644
index 0000000..dcf9efa
--- /dev/null
+++ b/examples/basics/visuals/mesh.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of Mesh visual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene.visuals import Mesh
+from vispy.geometry import create_sphere
+from vispy.scene.transforms import (STTransform, AffineTransform,
+                                    ChainTransform)
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.meshes = []
+        self.rotation = AffineTransform()
+
+        # Generate some data to work with
+        global mdata
+        mdata = create_sphere(20, 40, 1.0)
+
+        # Mesh with pre-indexed vertices, uniform color
+        self.meshes.append(Mesh(meshdata=mdata, color='r'))
+        #mesh.transform = STTransform(scale=(1, 1, .001), translate=(400, 400))
+
+        ## Mesh with pre-indexed vertices, per-face color
+        ##   Because vertices are pre-indexed, we get a different color
+        ##   every time a vertex is visited, resulting in sharp color
+        ##   differences between edges.
+        verts = mdata.vertices(indexed='faces')
+        nf = verts.size//9
+        fcolor = np.ones((nf, 3, 4), dtype=np.float32)
+        fcolor[..., 0] = np.linspace(1, 0, nf)[:, np.newaxis]
+        fcolor[..., 1] = np.random.normal(size=nf)[:, np.newaxis]
+        fcolor[..., 2] = np.linspace(0, 1, nf)[:, np.newaxis]
+        mesh = Mesh(vertices=verts, face_colors=fcolor)
+        self.meshes.append(mesh)
+
+        ## Mesh with unindexed vertices, per-vertex color
+        ##   Because vertices are unindexed, we get the same color
+        ##   every time a vertex is visited, resulting in no color differences
+        ##   between edges.
+        verts = mdata.vertices()
+        faces = mdata.faces()
+        nv = verts.size//3
+        vcolor = np.ones((nv, 4), dtype=np.float32)
+        vcolor[:, 0] = np.linspace(1, 0, nv)
+        vcolor[:, 1] = np.random.normal(size=nv)
+        vcolor[:, 2] = np.linspace(0, 1, nv)
+        self.meshes.append(Mesh(verts, faces, vcolor))
+        self.meshes.append(Mesh(verts, faces, vcolor, shading='flat'))
+        self.meshes.append(Mesh(verts, faces, vcolor, shading='smooth'))
+
+        # Lay out meshes in a grid
+        grid = (3, 3)
+        s = 300. / max(grid)
+        for i, mesh in enumerate(self.meshes):
+            x = 800. * (i % grid[0]) / grid[0] + 400. / grid[0] - 2
+            y = 800. * (i // grid[1]) / grid[1] + 400. / grid[1] + 2
+            mesh.transform = ChainTransform([STTransform(translate=(x, y),
+                                                         scale=(s, s, 1)),
+                                             self.rotation])
+
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+
+        self.size = (800, 800)
+        self.show()
+
+        self.timer = vispy.app.Timer(connect=self.rotate)
+        self.timer.start(0.016)
+
+    def rotate(self, event):
+        self.rotation.rotate(1, (0, 1, 0))
+        # TODO: altering rotation should trigger this automatically.
+        for m in self.meshes:
+            m._program._need_build = True
+        self.update()
+
+    def on_draw(self, ev):
+        gloo.set_clear_color('black')
+        gloo.clear(color=True, depth=True)
+        for mesh in self.meshes:
+            self.draw_visual(mesh)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/modular_components.py b/examples/basics/visuals/modular_components.py
new file mode 100644
index 0000000..438a154
--- /dev/null
+++ b/examples/basics/visuals/modular_components.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Demonstrates plugging custom shaders in to a ModularLine visual.
+
+This allows to modify the appearance of the visual without modifying or
+subclassing the original ModularLine class.
+"""
+
+import numpy as np
+import vispy.app
+import vispy.gloo as gloo
+from vispy.scene.visuals.modular_line import ModularLine
+from vispy.scene.transforms import BaseTransform, STTransform, arg_to_array
+from vispy.scene.components import (VisualComponent, VertexColorComponent,
+                                    XYPosComponent)
+from vispy.scene.shaders import Varying
+
+# vertex positions of data to draw
+N = 50
+pos = np.zeros((N, 2), dtype=np.float32)
+pos[:, 0] = np.linspace(-0.9, 0.9, N)
+pos[:, 1] = np.random.normal(size=N, scale=0.2).astype(np.float32)
+
+# One array of colors
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+
+# A custom Transform
+class SineTransform(BaseTransform):
+    """
+    Add sine wave to y-value for wavy effect.
+    """
+    glsl_map = """
+        vec4 sineTransform(vec4 pos) {
+            return vec4(pos.x, pos.y + sin(pos.x), pos.z, 1);
+        }"""
+
+    @arg_to_array
+    def map(self, coords):
+        ret = coords.copy()
+        ret[..., 1] += np.sin(ret[..., 0])
+        return ret
+
+    @arg_to_array
+    def imap(self, coords):
+        ret = coords.copy()
+        ret[..., 1] -= np.sin(ret[..., 0])
+        return ret
+
+
+# Custom color component
+class DashComponent(VisualComponent):
+    """
+    VisualComponent that adds dashing to an attached LineVisual.
+    """
+
+    SHADERS = dict(
+        frag_color="""
+            vec4 dash(vec4 color) {
+                float mod = $distance / $dash_len;
+                mod = mod - float(int(mod));
+                color.a = 0.5 * sin(mod*3.141593*2.) + 0.5;
+                return color;
+            }
+        """,
+        vert_post_hook="""
+            void dashSup() {
+                $output_dist = $distance_attr;
+            }
+        """)
+
+    def __init__(self, pos):
+        super(DashComponent, self).__init__()
+        self._vbo = None
+        self.pos = pos
+
+    def _make_vbo(self):
+        if self._vbo is None:
+            # measure distance along line
+            # TODO: this should be recomputed if the line data changes.
+            pixel_tr = self.visual.transform
+            pixel_pos = pixel_tr.map(self.pos)
+            dist = np.empty(pos.shape[0], dtype=np.float32)
+            diff = ((pixel_pos[1:] - pixel_pos[:-1]) ** 2).sum(axis=1) ** 0.5
+            dist[0] = 0.0
+            dist[1:] = np.cumsum(diff)
+            self._vbo = gloo.VertexBuffer(dist)
+        return self._vbo
+
+    def activate(self, program, mode):
+        vf = self._funcs['vert_post_hook']
+        ff = self._funcs['frag_color']
+        vf['distance_attr'] = self._make_vbo()  # attribute float
+        vf['output_dist'] = Varying('output_dist', dtype='float')
+        ff['dash_len'] = 20.
+        ff['distance'] = vf['output_dist']
+
+    @property
+    def supported_draw_modes(self):
+        return set((self.DRAW_PRE_INDEXED,))
+
+
+# custom position component
+class WobbleComponent(VisualComponent):
+    """
+    Give all vertices a wobble with random phase.
+    """
+    SHADERS = dict(
+        local_position="""
+            vec4 wobble(vec4 pos) {
+                float x = pos.x + 0.01 * cos($theta + $phase);
+                float y = pos.y + 0.01 * sin($theta + $phase);
+                return vec4(x, y, pos.z, pos.w);
+            }
+        """)
+
+    def __init__(self, pos):
+        super(WobbleComponent, self).__init__()
+        self._vbo = None
+        self.pos = pos
+        self.theta = (np.random.random(size=pos.shape[:-1]).astype(np.float32)
+                      * (2. * np.pi))
+        self.phase = 0
+
+    def activate(self, program, mode):
+        if self._vbo is None:
+            self._vbo = gloo.VertexBuffer(self.theta)
+
+        pf = self._funcs['local_position']
+        pf['theta'] = self._vbo
+        pf['phase'] = self.phase
+
+        # TODO: make this automatic
+        self._visual._program._need_build = True
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+
+        self.line = ModularLine()
+        self.line.transform = (STTransform(scale=(40, 100), 
+                                           translate=(400, 400)) *
+                               SineTransform() *
+                               STTransform(scale=(10, 3)))
+        self.wobbler = WobbleComponent(pos)
+        self.line.pos_components = [XYPosComponent(pos), self.wobbler]
+        dasher = DashComponent(pos)
+        self.line.color_components = [VertexColorComponent(color), dasher]
+
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+
+        self.timer = vispy.app.Timer(connect=self.wobble,
+                                     interval=0.02,
+                                     start=True)
+
+    def on_draw(self, ev):
+        gloo.set_clear_color('black')
+        gloo.clear(color=True, depth=True)
+
+        self.draw_visual(self.line)
+
+    def wobble(self, ev):
+        self.wobbler.phase += 0.1
+        self.update()
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/modular_line.py b/examples/basics/visuals/modular_line.py
new file mode 100644
index 0000000..ae48dd7
--- /dev/null
+++ b/examples/basics/visuals/modular_line.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of LineVisual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene.visuals.modular_line import ModularLine
+
+# vertex positions of data to draw
+N = 200
+pos = np.zeros((N, 3), dtype=np.float32)
+pos[:, 0] = np.linspace(100, 700, N)
+pos[:, 1] = np.random.normal(size=N, scale=100, loc=400)
+
+# color array
+color = np.ones((N, 4), dtype=np.float32)
+color[:, 0] = np.linspace(0, 1, N)
+color[:, 1] = color[::-1, 0]
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.line = ModularLine(pos=pos, color=color)
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+
+    def on_draw(self, ev):
+        gloo.set_clear_color('black')
+        gloo.clear(color=True, depth=True)
+        gloo.set_viewport(0, 0, *self.size)
+        self.draw_visual(self.line)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/modular_mesh.py b/examples/basics/visuals/modular_mesh.py
new file mode 100644
index 0000000..c6fd163
--- /dev/null
+++ b/examples/basics/visuals/modular_mesh.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# vispy: gallery 30
+
+"""
+Simple demonstration of LineVisual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene.visuals.modular_mesh import ModularMesh
+from vispy.scene.components import (VertexColorComponent, GridContourComponent,
+                                    VertexNormalComponent, ShadingComponent)
+from vispy.geometry import create_sphere
+from vispy.scene.transforms import (STTransform, AffineTransform,
+                                    ChainTransform)
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.meshes = []
+        self.rotation = AffineTransform()
+
+        # Generate some data to work with
+        global mdata
+        mdata = create_sphere(20, 40, 1.0)
+
+        # Mesh with pre-indexed vertices, uniform color
+        verts = mdata.vertices(indexed='faces')
+        mesh = ModularMesh(pos=verts, color=(1, 0, 0, 1))
+        self.meshes.append(mesh)
+
+        # Mesh with pre-indexed vertices, per-face color
+        #   Because vertices are pre-indexed, we get a different color
+        #   every time a vertex is visited, resulting in sharp color
+        #   differences between edges.
+        nf = verts.size//9
+        fcolor = np.ones((nf, 3, 4), dtype=np.float32)
+        fcolor[..., 0] = np.linspace(1, 0, nf)[:, np.newaxis]
+        fcolor[..., 1] = np.random.normal(size=nf)[:, np.newaxis]
+        fcolor[..., 2] = np.linspace(0, 1, nf)[:, np.newaxis]
+        mesh = ModularMesh(pos=verts, color=fcolor)
+        self.meshes.append(mesh)
+
+        # Mesh with unindexed vertices, per-vertex color
+        #   Because vertices are unindexed, we get the same color
+        #   every time a vertex is visited, resulting in no color differences
+        #   between edges.
+        verts = mdata.vertices()
+        faces = mdata.faces()
+        nv = verts.size//3
+        vcolor = np.ones((nv, 4), dtype=np.float32)
+        vcolor[:, 0] = np.linspace(1, 0, nv)
+        vcolor[:, 1] = np.random.normal(size=nv)
+        vcolor[:, 2] = np.linspace(0, 1, nv)
+        mesh = ModularMesh(pos=verts, faces=faces, color=vcolor)
+        self.meshes.append(mesh)
+
+        # Mesh colored by vertices + grid contours
+        mesh = ModularMesh(pos=verts, faces=faces)
+        mesh.color_components = [VertexColorComponent(vcolor),
+                                 GridContourComponent(spacing=(0.13, 0.13,
+                                                               0.13))]
+        self.meshes.append(mesh)
+
+        # Phong shaded mesh
+        mesh = ModularMesh(pos=verts, faces=faces)
+        normal_comp = VertexNormalComponent(mdata)
+        mesh.color_components = [VertexColorComponent(vcolor),
+                                 GridContourComponent(spacing=(0.1, 0.1, 0.1)),
+                                 ShadingComponent(normal_comp,
+                                                  lights=[((-1, 1, -1),
+                                                          (1.0, 1.0, 1.0))],
+                                                  ambient=0.2)]
+        self.meshes.append(mesh)
+
+        # Phong shaded mesh, flat faces
+        mesh = ModularMesh(pos=mdata.vertices(indexed='faces'))
+        normal_comp = VertexNormalComponent(mdata, smooth=False)
+        mesh.color_components = [VertexColorComponent(vcolor[mdata.faces()]),
+                                 GridContourComponent(spacing=(0.1, 0.1, 0.1)),
+                                 ShadingComponent(normal_comp,
+                                                  lights=[((-1, 1, -1),
+                                                           (1.0, 1.0, 1.0))],
+                                                  ambient=0.2)]
+        self.meshes.append(mesh)
+
+        # Lay out meshes in a grid
+        grid = (3, 3)
+        s = 300. / max(grid)
+        for i, mesh in enumerate(self.meshes):
+            x = 800. * (i % grid[0]) / grid[0] + 400. / grid[0] - 2
+            y = 800. * (i // grid[1]) / grid[1] + 400. / grid[1] + 2
+            mesh.transform = ChainTransform([STTransform(translate=(x, y),
+                                                         scale=(s, s, 1)),
+                                             self.rotation])
+
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+
+        self.size = (800, 800)
+        self.show()
+
+        self.timer = vispy.app.Timer(connect=self.rotate)
+        self.timer.start(0.016)
+
+    def rotate(self, event):
+        self.rotation.rotate(1, (0, 1, 0))
+        # TODO: altering rotation should trigger this automatically.
+        for m in self.meshes:
+            m._program._need_build = True
+        self.update()
+
+    def on_draw(self, ev):
+        gloo.set_clear_color('black')
+        gloo.clear(color=True, depth=True)
+        for mesh in self.meshes:
+            self.draw_visual(mesh)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/modular_point.py b/examples/basics/visuals/modular_point.py
new file mode 100644
index 0000000..41e83be
--- /dev/null
+++ b/examples/basics/visuals/modular_point.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of PointsVisual.
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene.visuals.modular_point import ModularPoint
+
+# vertex positions of data to draw
+N = 200
+pos = np.zeros((N, 3), dtype=np.float32)
+pos[:, 0] = np.linspace(50., 750., N)
+pos[:, 1] = np.random.normal(size=N, scale=100, loc=400).astype(np.float32)
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.points = ModularPoint(pos, color=(0, 1, 0, 1))
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+
+    def on_draw(self, ev):
+        gloo.set_clear_color('black')
+        gloo.clear(color=True, depth=True)
+        gloo.set_viewport(0, 0, *self.size)
+        self.draw_visual(self.points)
+
+
+if __name__ == '__main__':
+    win = Canvas()
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/polygon_visual.py b/examples/basics/visuals/polygon_visual.py
new file mode 100644
index 0000000..8096482
--- /dev/null
+++ b/examples/basics/visuals/polygon_visual.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Demonstration of Polygon and subclasses
+"""
+
+import numpy as np
+import vispy.app
+from vispy import gloo
+from vispy.scene import visuals, transforms
+
+# vertex positions of polygon data to draw
+pos = np.array([[0, 0, 0],
+               [0.25, 0.22, 0],
+               [0.25, 0.5, 0],
+               [0, 0.5, 0],
+               [-0.25, 0.25, 0]])
+
+pos = np.array([[0, 0],
+                [10, 0],
+                [10, 10],
+                [20, 10],
+                [20, 20],
+                [25, 20],
+                [25, 25],
+                [20, 25],
+                [20, 20],
+                [10, 17],
+                [5, 25],
+                [9, 30],
+                [6, 15],
+                [15, 12.5],
+                [0, 5]])
+
+theta = np.linspace(0, 2*np.pi, 11)
+pos = np.hstack([np.cos(theta)[:, np.newaxis], 
+                 np.sin(theta)[:, np.newaxis]])
+pos[::2] *= 0.4
+pos[-1] = pos[0]
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        global pos
+        
+        self.visuals = []
+        
+        polygon = visuals.Polygon(pos=pos, color=(0.8, .2, 0, 1),
+                                  border_color=(1, 1, 1, 1))
+        polygon.transform = transforms.STTransform(
+            scale=(200, 200),
+            translate=(600, 600))
+        self.visuals.append(polygon)
+        
+        ellipse = visuals.Ellipse(pos=(0, 0, 0), radius=(100, 150),
+                                  color=(0.2, 0.2, 0.8, 1),
+                                  border_color=(1, 1, 1, 1),
+                                  start_angle=180., span_angle=150.)
+        ellipse.transform = transforms.STTransform(scale=(0.9, 1.5),
+                                                   translate=(200, 300))
+        self.visuals.append(ellipse)
+
+        rect = visuals.Rectangle(pos=(600, 200, 0), height=200.,
+                                 width=300.,
+                                 radius=[30., 30., 0., 0.],
+                                 color=(0.5, 0.5, 0.2, 1),
+                                 border_color='white')
+        self.visuals.append(rect)
+
+        rpolygon = visuals.RegularPolygon(pos=(200., 600., 0), radius=160,
+                                          color=(0.2, 0.8, 0.2, 1),
+                                          border_color=(1, 1, 1, 1),
+                                          sides=6)
+        self.visuals.append(rpolygon)
+        
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+        
+    def on_draw(self, ev):
+        gloo.set_clear_color((0, 0, 0, 1))
+        gloo.clear()
+        for vis in self.visuals:
+            self.draw_visual(vis)
+        
+
+if __name__ == '__main__':
+    win = Canvas() 
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/reactive_ellipse.py b/examples/basics/visuals/reactive_ellipse.py
new file mode 100644
index 0000000..1c402b3
--- /dev/null
+++ b/examples/basics/visuals/reactive_ellipse.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of reactive EllipseVisual. 
+"""
+
+import vispy
+from vispy import gloo, app
+from vispy.scene import visuals
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.ellipse = visuals.Ellipse(pos=(400, 400, 0), radius=[320, 240],
+                                       color=(1, 0, 0, 1),
+                                       border_color=(1, 1, 1, 1),
+                                       start_angle=180., span_angle=150.)
+        
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+        
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_timer(self, event):
+        self.ellipse.radius[0] += 1
+        self.ellipse.radius[1] += 1.5
+        self.ellipse.span_angle += 0.6
+        self.update()
+
+    def on_mouse_press(self, event):
+        self.ellipse.radius = [320, 240]
+        self.ellipse.span_angle = 150.
+        self.update()
+
+    def on_draw(self, ev):
+        gloo.clear(color='black')
+        self.draw_visual(self.ellipse)
+        
+
+if __name__ == '__main__':
+    win = Canvas() 
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/reactive_polygon.py b/examples/basics/visuals/reactive_polygon.py
new file mode 100644
index 0000000..54008e4
--- /dev/null
+++ b/examples/basics/visuals/reactive_polygon.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of PolygonVisual. 
+"""
+
+import numpy as np
+import vispy
+from vispy import gloo, app
+from vispy.scene import visuals
+
+# vertex positions of data to draw
+pos = [[0, 0, 0],
+       [0.25, 0.22, 0],
+       [0.25, 0.5, 0],
+       [0, 0.5, 0],
+       [-0.25, 0.25, 0]]
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.polygon = visuals.Polygon(pos=pos, color=(1, 0, 0, 1),
+                                       border_color=(1, 1, 1, 1))
+        self.polygon.transform = vispy.scene.transforms.STTransform(
+            scale=(500, 500),
+            translate=(400, 400))
+        
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.pos = np.array(pos)
+        self.i = 1
+        self.size = (800, 800)
+        self.show()
+
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_timer(self, event):
+        self.pos[0] += [self.i, 0.0, 0.0]
+        self.i *= -0.92
+        self.polygon.pos = self.pos
+        self.update()
+
+    def on_mouse_press(self, event):
+        self.i = 1.
+        self.pos = np.array(pos)
+        self.update()
+
+    def on_draw(self, ev):
+        gloo.clear(color='black')
+        self.draw_visual(self.polygon)
+        
+
+if __name__ == '__main__':
+    win = Canvas() 
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/reactive_regular_polygon.py b/examples/basics/visuals/reactive_regular_polygon.py
new file mode 100644
index 0000000..5ce491e
--- /dev/null
+++ b/examples/basics/visuals/reactive_regular_polygon.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Simple demonstration of reactive RegularPolygonVisual. 
+"""
+
+import vispy
+from vispy import gloo, app
+from vispy.scene import visuals
+import numpy as np
+
+
+class Canvas(vispy.scene.SceneCanvas):
+    def __init__(self):
+        self.rpolygon = visuals.RegularPolygon(pos=(400.0, 400.0, 0), 
+                                               radius=80.,
+                                               color=(1, 0, 0, 1),
+                                               border_color=(1, 1, 1, 1),
+                                               sides=4)
+        
+        vispy.scene.SceneCanvas.__init__(self, keys='interactive')
+        self.size = (800, 800)
+        self.show()
+        
+        self.rfactor = 0.01
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_timer(self, event):
+        if (self.rpolygon.radius > 400. or self.rpolygon.radius < 80.):
+            self.rfactor *= -1
+        self.rpolygon.radius += self.rfactor
+        self.rpolygon.sides += 100 * self.rfactor
+        self.rpolygon.color = (np.sin(self.rpolygon.radius * 0.00625), 0.5,
+                               np.cos(self.rpolygon.radius * 0.005), 1.0)
+        self.update()
+
+    def on_mouse_press(self, event):
+        self.rpolygon.radius = 80.
+        self.rpolygon.sides = 4
+        self.rpolygon.color = 'red'
+        self.update()
+
+    def on_draw(self, ev):
+        gloo.clear(color='black')
+        gloo.set_viewport(0, 0, *self.size)
+        self.draw_visual(self.rpolygon)
+        
+
+if __name__ == '__main__':
+    win = Canvas() 
+    import sys
+    if sys.flags.interactive != 1:
+        vispy.app.run()
diff --git a/examples/basics/visuals/text_visual.py b/examples/basics/visuals/text_visual.py
new file mode 100644
index 0000000..1bb6b02
--- /dev/null
+++ b/examples/basics/visuals/text_visual.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from vispy import scene, gloo
+from vispy.scene.visuals import Text
+
+
+class Canvas(scene.SceneCanvas):
+    def __init__(self):
+        scene.SceneCanvas.__init__(self, title='Glyphs', keys='interactive')
+        self.font_size = 48.
+        self.text = Text('', bold=True)
+        self.apply_zoom()
+
+    def on_draw(self, event):
+        gloo.clear(color='white')
+        self.draw_visual(self.text)
+
+    def on_mouse_wheel(self, event):
+        """Use the mouse wheel to zoom."""
+        self.font_size *= 1.25 if event.delta[1] > 0 else 0.8
+        self.font_size = max(min(self.font_size, 160.), 6.)
+        self.apply_zoom()
+
+    def on_resize(self, event):
+        self.apply_zoom()
+
+    def apply_zoom(self):
+        self.text.text = '%s pt' % round(self.font_size, 1)
+        self.text.font_size = self.font_size
+        self.text.pos = self.size[0] // 2, self.size[1] // 2
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    c.app.run()
diff --git a/examples/benchmark/simple-vispy.py b/examples/benchmark/simple-vispy.py
deleted file mode 100755
index 813a3ed..0000000
--- a/examples/benchmark/simple-vispy.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/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/benchmark/simple-glut.py b/examples/benchmark/simple_glut.py
similarity index 64%
rename from examples/benchmark/simple-glut.py
rename to examples/benchmark/simple_glut.py
index 9896145..5773d87 100755
--- a/examples/benchmark/simple-glut.py
+++ b/examples/benchmark/simple_glut.py
@@ -1,29 +1,31 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team All rights reserved.
+# Copyright (c) 2014, Vispy Development Team.
 # 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 )
+    t = glut.glutGet(glut.GLUT_ELAPSED_TIME)
     frames = frames + 1
-    elapsed = (t-t0)/1000.0
+    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
+        print("FPS : %.2f (%d frames in %.2f second)"
+              % (frames / elapsed, frames, elapsed))
+        t0, frames = t, 0
     glut.glutPostRedisplay()
 
 
@@ -32,12 +34,13 @@ if __name__ == '__main__':
     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.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
+    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..1ca8d75
--- /dev/null
+++ b/examples/benchmark/simple_vispy.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from vispy import app
+from vispy.gloo import clear
+
+# app.use_app('pyqt4')  # or pyside, glut, pyglet, sdl2, etc.
+
+canvas = app.Canvas(size=(512, 512), title = "Do nothing benchmark (vispy)",
+                    keys='interactive')
+
+
+ at canvas.connect
+def on_draw(event):
+    clear(color=True, depth=True)
+    canvas.update()  # Draw frames as fast as possible
+
+canvas.show()
+canvas.measure_fps()
+app.run()
diff --git a/examples/demo/atom.py b/examples/demo/atom.py
deleted file mode 100644
index 6f2bb2a..0000000
--- a/examples/demo/atom.py
+++ /dev/null
@@ -1,192 +0,0 @@
-#!/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/game_of_life.py b/examples/demo/game_of_life.py
deleted file mode 100644
index 748d83b..0000000
--- a/examples/demo/game_of_life.py
+++ /dev/null
@@ -1,156 +0,0 @@
-#!/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/gloo/atom.py b/examples/demo/gloo/atom.py
new file mode 100644
index 0000000..0e4a254
--- /dev/null
+++ b/examples/demo/gloo/atom.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   06/03/2014
+# Abstract: Fake electrons orbiting
+# Keywords: Sprites, atom, particles
+# -----------------------------------------------------------------------------
+
+import numpy as np
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective, translate, rotate
+
+# Create vertices
+n, p = 100, 150
+data = np.zeros(p * n, [('a_position', np.float32, 2),
+                        ('a_color',    np.float32, 4),
+                        ('a_rotation', np.float32, 4)])
+trail = .5 * np.pi
+data['a_position'][:, 0] = np.resize(np.linspace(0, trail, n), p * n)
+data['a_position'][:, 0] += np.repeat(np.random.uniform(0, 2 * np.pi, p), n)
+data['a_position'][:, 1] = np.repeat(np.linspace(0, 2 * np.pi, p), n)
+
+data['a_color'] = 1, 1, 1, 1
+data['a_color'] = np.repeat(
+    np.random.uniform(0.75, 1.00, (p, 4)).astype(np.float32), n, axis=0)
+data['a_color'][:, 3] = np.resize(np.linspace(0, 1, n), p * n)
+
+data['a_rotation'] = np.repeat(
+    np.random.uniform(0, 2 * np.pi, (p, 4)).astype(np.float32), n, axis=0)
+
+
+vert = """
+#version 120
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_size;
+uniform float u_clock;
+
+attribute vec2 a_position;
+attribute vec4 a_color;
+attribute vec4 a_rotation;
+varying vec4 v_color;
+
+mat4 build_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_color = a_color;
+
+    float x0 = 1.5;
+    float z0 = 0.0;
+
+    float theta = a_position.x + u_clock;
+    float x1 = x0*cos(theta) + z0*sin(theta);
+    float y1 = 0.0;
+    float z1 = (z0*cos(theta) - x0*sin(theta))/2.0;
+
+    mat4 R = build_rotation(a_rotation.xyz, a_rotation.w);
+    gl_Position = u_projection * u_view * u_model * R * vec4(x1,y1,z1,1);
+    gl_PointSize = 8.0 * u_size * sqrt(v_color.a);
+}
+"""
+
+frag = """
+#version 120
+varying vec4 v_color;
+varying float v_size;
+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));
+}
+"""
+
+
+# ------------------------------------------------------------ Canvas class ---
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+        self.size = 800, 800
+        self.title = "Atom [zoom with mouse scroll"
+
+        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 = 6.5
+        translate(self.view, 0, 0, -self.translate)
+
+        self.program.bind(gloo.VertexBuffer(data))
+        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.clock = 0
+        self.stop_rotation = False
+
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_initialize(self, event):
+        gloo.set_state('translucent', depth_test=False)
+
+    def on_key_press(self, event):
+        if event.text == ' ':
+            self.stop_rotation = not self.stop_rotation
+
+    def on_timer(self, event):
+        if not self.stop_rotation:
+            self.theta += .05
+            self.phi += .05
+            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.clock += np.pi / 100
+        self.program['u_clock'] = self.clock
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(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_draw(self, event):
+        gloo.clear('black')
+        self.program.draw('points')
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/boids.py b/examples/demo/gloo/boids.py
similarity index 61%
rename from examples/demo/boids.py
rename to examples/demo/gloo/boids.py
index e9d4367..2d62b15 100644
--- a/examples/demo/boids.py
+++ b/examples/demo/gloo/boids.py
@@ -1,8 +1,8 @@
-# #!/usr/bin/env python
+# !/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.
@@ -14,34 +14,34 @@ 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]
+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['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
+boids['color'] = 1, 1, 1, 1
 
 target['size'] = 16
-target['color'][:] = 1,1,0,1
+target['color'][:] = 1, 1, 0, 1
 predator['size'] = 16
-predator['color'][:] = 1,0,0,1
+predator['color'][:] = 1, 0, 0, 1
 target['position'][:] = 0.25, 0.0, 0
 
 VERT_SHADER = """
+#version 120
 attribute vec3 position;
 attribute vec4 color;
 attribute float size;
@@ -55,9 +55,10 @@ void main (void) {
 """
 
 FRAG_SHADER = """
+#version 120
 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);
@@ -68,113 +69,111 @@ void main()
 
 
 class Canvas(app.Canvas):
-    
+
     def __init__(self):
-        app.Canvas.__init__(self)
-        
+        app.Canvas.__init__(self, keys='interactive')
+
         # 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'])
+        self.vbo_position = gloo.VertexBuffer(particles['position'].copy())
+        self.vbo_color = gloo.VertexBuffer(particles['color'].copy())
+        self.vbo_size = gloo.VertexBuffer(particles['size'].copy())
 
         # 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)
+        
+        self._timer = app.Timer('auto', connect=self.update, start=True)
     
+    def on_initialize(self, event):
+        gloo.set_state(clear_color=(0, 0, 0, 1), blend=True,
+                       blend_func=('src_alpha', 'one'))
+
     def on_resize(self, event):
         width, height = event.size
-        gl.glViewport(0, 0, width, height)
-        
+        gloo.set_viewport(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)
-        
+        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:
+        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)
-        
+    def on_draw(self, event):
+        gloo.clear()
         # Draw
-        self.program.draw(gl.GL_POINTS)
-        
+        self.program.draw('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
+        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
+
+        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)
-    
+
+        # 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)
-    
+        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)
-    
+        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)
+        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['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
@@ -184,4 +183,3 @@ if __name__ == '__main__':
     c = Canvas()
     c.show()
     app.run()
-    
diff --git a/examples/demo/gloo/brain.py b/examples/demo/gloo/brain.py
new file mode 100644
index 0000000..eb5859d
--- /dev/null
+++ b/examples/demo/gloo/brain.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+3D brain mesh viewer.
+"""
+
+from timeit import default_timer
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.io import load_data_file
+
+brain = np.load(load_data_file('brain/brain.npz'))
+data = brain['vertex_buffer']
+faces = brain['index_buffer']
+
+VERT_SHADER = """
+#version 120
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform vec4 u_color;
+
+attribute vec3 a_position;
+attribute vec3 a_normal;
+attribute vec4 a_color;
+
+varying vec3 v_position;
+varying vec3 v_normal;
+varying vec4 v_color;
+
+void main()
+{
+    v_normal = a_normal;
+    v_position = a_position;
+    v_color = a_color * u_color;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_normal;
+
+uniform vec3 u_light_intensity;
+uniform vec3 u_light_position;
+
+varying vec3 v_position;
+varying vec3 v_normal;
+varying vec4 v_color;
+
+void main()
+{
+    // Calculate normal in world coordinates
+    vec3 normal = normalize(u_normal * vec4(v_normal,1.0)).xyz;
+
+    // Calculate the location of this fragment (pixel) in world coordinates
+    vec3 position = vec3(u_view*u_model * vec4(v_position, 1));
+
+    // Calculate the vector from this pixels surface to the light source
+    vec3 surfaceToLight = u_light_position - position;
+
+    // Calculate the cosine of the angle of incidence (brightness)
+    float brightness = dot(normal, surfaceToLight) /
+                      (length(surfaceToLight) * length(normal));
+    brightness = max(min(brightness,1.0),0.0);
+
+    // Calculate final color of the pixel, based on:
+    // 1. The angle of incidence: brightness
+    // 2. The color/intensities of the light: light.intensities
+    // 3. The texture and texture coord: texture(tex, fragTexCoord)
+
+    // Specular lighting.
+    vec3 surfaceToCamera = vec3(0.0, 0.0, 1.0) - position;
+    vec3 K = normalize(normalize(surfaceToLight) + normalize(surfaceToCamera));
+    float specular = clamp(pow(abs(dot(normal, K)), 40.), 0.0, 1.0);
+    
+    gl_FragColor = v_color * brightness * vec4(u_light_intensity, 1);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+        self.size = 800, 600
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        
+        self.theta, self.phi = -80, 180
+        self.translate = 3
+
+        self.faces = gloo.IndexBuffer(faces)
+        self.program.bind(gloo.VertexBuffer(data))
+        
+        self.program['u_color'] = 1, 1, 1, 1
+        self.program['u_light_position'] = (1., 1., 1.)
+        self.program['u_light_intensity'] = (1., 1., 1.)
+        
+        self._t0 = default_timer()
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+        
+        self.update_matrices()
+
+    def update_matrices(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)
+        
+        rotate(self.model, self.theta, 1, 0, 0)
+        rotate(self.model, self.phi, 0, 1, 0)
+        
+        translate(self.view, 0, 0, -self.translate)
+        
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.program['u_normal'] = np.array(np.matrix(np.dot(self.view, 
+                                                             self.model)).I.T)
+        
+    def on_initialize(self, event):
+        gloo.set_state(blend=False, depth_test=True, polygon_offset_fill=True)
+
+    def on_timer(self, event):
+        elapsed = default_timer() - self._t0
+        self.phi = 180 + elapsed * 50.
+        self.update_matrices()
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        self.projection = perspective(45.0, width / float(height), 1.0, 20.0)
+        self.program['u_projection'] = self.projection
+
+    def on_mouse_wheel(self, event):
+        self.translate += -event.delta[1]/5.
+        self.translate = max(2, self.translate)
+        self.update_matrices()
+        self.update()
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('triangles', indices=self.faces)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/gloo/camera.py b/examples/demo/gloo/camera.py
new file mode 100644
index 0000000..a86b231
--- /dev/null
+++ b/examples/demo/gloo/camera.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""Display a live webcam feed. Require OpenCV (Python 2 only).
+"""
+
+try:
+    import cv2
+except Exception:
+    raise ImportError("You need OpenCV for this example.")
+    
+import numpy as np
+from vispy import app
+from vispy import gloo
+
+vertex = """
+    attribute vec2 position;
+    attribute vec2 texcoord;
+    varying vec2 v_texcoord;
+    void main()
+    {
+        gl_Position = vec4(position, 0.0, 1.0);
+        v_texcoord = texcoord;
+    }
+"""
+
+fragment = """
+    uniform sampler2D texture;
+    varying vec2 v_texcoord;
+    void main()
+    {
+        gl_FragColor = texture2D(texture, v_texcoord);
+        
+        // HACK: the image is in BGR instead of RGB.
+        float temp = gl_FragColor.r;
+        gl_FragColor.r = gl_FragColor.b;
+        gl_FragColor.b = temp;
+    }
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(640, 480), keys='interactive')
+        self.program = gloo.Program(vertex, fragment, count=4)
+        self.program['position'] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        self.program['texcoord'] = [(1, 1), (1, 0), (0, 1), (0, 0)]
+        self.program['texture'] = np.zeros((480, 640, 3)).astype(np.uint8)
+        self.cap = cv2.VideoCapture(0)
+        if not self.cap.isOpened():
+            raise Exception("There's no available camera.")
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+        gloo.clear('black')
+        _, im = self.cap.read()
+        self.program['texture'][...] = im
+        self.program.draw('triangle_strip')
+        
+    def on_timer(self, event):
+        self.update()
+        
+c = Canvas()
+c.show()
+app.run()
+c.cap.release()
diff --git a/examples/demo/cloud.py b/examples/demo/gloo/cloud.py
similarity index 68%
rename from examples/demo/cloud.py
rename to examples/demo/gloo/cloud.py
index 0c86d6c..97e0d06 100644
--- a/examples/demo/cloud.py
+++ b/examples/demo/gloo/cloud.py
@@ -1,4 +1,4 @@
-# #!/usr/bin/env python
+# !/usr/bin/env python
 # -*- coding: utf-8 -*-
 # vispy: gallery 1
 
@@ -10,26 +10,27 @@ 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 
+# 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)
+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 = """
+#version 120
+
 // Uniforms
 // ------------------------------------
 uniform mat4 u_model;
@@ -66,6 +67,8 @@ void main (void) {
 """
 
 frag = """
+#version 120
+
 // Constants
 // ------------------------------------
 
@@ -92,8 +95,8 @@ float disc(vec2 P, float size)
 // ----------------
 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 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;
 }
@@ -131,7 +134,8 @@ float clober(vec2 P, float size)
 // ----------------
 float square(vec2 P, float size)
 {
-    float r = max(abs(gl_PointCoord.x -.5)*size, abs(gl_PointCoord.y -.5)*size);
+    float r = max(abs(gl_PointCoord.x -.5)*size,
+                  abs(gl_PointCoord.y -.5)*size);
     r -= v_size/2;
     return r;
 }
@@ -139,7 +143,7 @@ float square(vec2 P, float size)
 // ----------------
 float diamond(vec2 P, float size)
 {
-    float r = abs(gl_PointCoord.x -.5)*size + abs(gl_PointCoord.y -.5)*size; 
+    float r = abs(gl_PointCoord.x -.5)*size + abs(gl_PointCoord.y -.5)*size;
     r -= v_size/2;
     return r;
 }
@@ -147,8 +151,10 @@ float diamond(vec2 P, float size)
 // ----------------
 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 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;
@@ -157,8 +163,10 @@ float vbar(vec2 P, float size)
 // ----------------
 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 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;
@@ -167,9 +175,12 @@ float hbar(vec2 P, float size)
 // ----------------
 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 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;
@@ -179,7 +190,7 @@ float cross(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;
 
@@ -216,85 +227,70 @@ void main()
 """
 
 
-
 # ------------------------------------------------------------ Canvas class ---
 class Canvas(app.Canvas):
 
-
     def __init__(self):
-        app.Canvas.__init__(self)
+        app.Canvas.__init__(self, keys='interactive')
         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.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)
+        translate(self.view, 0, 0, -self.translate)
 
-        self.program.set_vars(gloo.VertexBuffer(data))
+        self.program.bind(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.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()
-
+        self.timer = app.Timer('auto', connect=self.on_timer, start=True)
 
     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)
-
+        gloo.set_state('translucent', clear_color='white')
 
-    def on_key_press(self,event):
+    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):
+    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)
+        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 )
+        gloo.set_viewport(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.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.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)
-
-
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
 
 
 if __name__ == '__main__':
diff --git a/examples/demo/gloo/donut.py b/examples/demo/gloo/donut.py
new file mode 100644
index 0000000..e0d5e64
--- /dev/null
+++ b/examples/demo/gloo/donut.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+"""
+Mesmerizing donut
+"""
+
+import numpy as np
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective, translate, rotate
+
+# Create vertices
+n, p = 50, 40
+data = np.zeros(p * n, [('a_position', np.float32, 2),
+                        ('a_bg_color', np.float32, 4),
+                        ('a_fg_color', np.float32, 4),
+                        ('a_size',     np.float32, 1)])
+data['a_position'][:, 0] = np.resize(np.linspace(0, 2 * np.pi, n), p * n)
+data['a_position'][:, 1] = np.repeat(np.linspace(0, 2 * np.pi, p), n)
+data['a_bg_color'] = np.random.uniform(0.75, 1.00, (n * p, 4))
+data['a_bg_color'][:, 3] = 1
+data['a_fg_color'] = 0, 0, 0, 1
+data['a_size'] = np.random.uniform(8, 8, n * p)
+u_linewidth = 1.0
+u_antialias = 1.0
+u_size = 1
+
+
+vert = """
+#version 120
+
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_linewidth;
+uniform float u_antialias;
+uniform float u_size;
+uniform float u_clock;
+
+attribute vec2  a_position;
+attribute vec4  a_fg_color;
+attribute vec4  a_bg_color;
+attribute float a_size;
+
+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;
+
+    float x0 = 0.5;
+    float z0 = 0.0;
+
+    float theta = a_position.x + u_clock;
+    float x1 = x0*cos(theta) + z0*sin(theta) - 1.0;
+    float y1 = 0.0;
+    float z1 = z0*cos(theta) - x0*sin(theta);
+
+    float phi = a_position.y;
+    float x2 = x1*cos(phi) + y1*sin(phi);
+    float y2 = y1*cos(phi) - x1*sin(phi);
+    float z2 = z1;
+
+    gl_Position = u_projection * u_view * u_model * vec4(x2,y2,z2,1);
+    gl_PointSize = v_size + 2*(v_linewidth + 1.5*v_antialias);
+}
+"""
+
+frag = """
+#version 120
+
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_size;
+varying float v_linewidth;
+varying float v_antialias;
+void main()
+{
+    float size = v_size +2*(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) - v_size/2;
+    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, keys='interactive')
+        self.size = 800, 800
+        self.title = "D'oh ! A big donut"
+
+        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.bind(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.clock = 0
+        self.stop_rotation = False
+
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def on_initialize(self, event):
+        gloo.set_state('translucent', clear_color='white')
+
+    def on_key_press(self, event):
+        if event.text == ' ':
+            self.stop_rotation = not self.stop_rotation
+
+    def on_timer(self, event):
+        if not self.stop_rotation:
+            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.clock += np.pi / 1000
+        self.program['u_clock'] = self.clock
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(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_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/fireworks.py b/examples/demo/gloo/fireworks.py
similarity index 71%
rename from examples/demo/fireworks.py
rename to examples/demo/gloo/fireworks.py
index b71d676..a439c34 100644
--- a/examples/demo/fireworks.py
+++ b/examples/demo/gloo/fireworks.py
@@ -14,30 +14,30 @@ 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))
+im1 = np.random.normal(
+    0.8, 0.3, (radius * 2 + 1, radius * 2 + 1)).astype(np.float32)
 
 # 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')
+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) ])
+data = np.zeros(N, [('a_lifetime', np.float32, 1),
+                    ('a_startPosition', np.float32, 3),
+                    ('a_endPosition', np.float32, 3)])
 
 
 VERT_SHADER = """
+#version 120
 uniform float u_time;
 uniform vec3 u_centerPosition;
 attribute float a_lifetime;
@@ -55,7 +55,7 @@ void main () {
     }
     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;
@@ -63,13 +63,15 @@ void main () {
 """
 
 FRAG_SHADER = """
+#version 120
+
 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;
@@ -79,67 +81,60 @@ void main()
 
 
 class Canvas(app.Canvas):
-    
+
     def __init__(self):
-        app.Canvas.__init__(self)
+        app.Canvas.__init__(self, keys='interactive')
         self.size = 800, 600
 
         # Create program
-        self._program = gloo.Program( VERT_SHADER, FRAG_SHADER)        
-        self._program.set_vars(gloo.VertexBuffer(data))
+        self._program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self._program.bind(gloo.VertexBuffer(data))
         self._program['s_texture'] = gloo.Texture2D(im1)
-        
+
         # Create first explosion
         self._new_explosion()
-    
+        
+        self._timer = app.Timer('auto', connect=self.update, start=True)
     
     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)
-    
-    
+        gloo.set_state(blend=True, clear_color='black',
+                       blend_func=('src_alpha', 'one'))
+
     def on_resize(self, event):
         width, height = event.size
-        gl.glViewport(0, 0, width, height)
-    
-    
-    def on_paint(self, event):
-        
+        gloo.set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+
         # Clear
-        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
-        
+        gloo.clear()
+
         # Draw
         self._program['u_time'] = time.time() - self._starttime
-        self._program.draw(gl.GL_POINTS)
-        
-        # Invoke a new draw
-        self.update()
-        
+        self._program.draw('points')
+
         # 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
+        alpha = 1.0 / N ** 0.08
         color = np.random.uniform(0.1, 0.9, (3,))
-        
-        self._program['u_color'] = tuple(color)+ (alpha,)
-        
+
+        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))
-        
+        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()
 
diff --git a/examples/demo/galaxy.py b/examples/demo/gloo/galaxy.py
similarity index 52%
rename from examples/demo/galaxy.py
rename to examples/demo/gloo/galaxy.py
index a4d68e4..49aa495 100644
--- a/examples/demo/galaxy.py
+++ b/examples/demo/gloo/galaxy.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # vispy: gallery 2
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
 """
@@ -9,50 +9,53 @@ 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
+
+
+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
+n = 3 * p
 
 data = np.zeros(n, [('a_position', np.float32, 3),
-                    ('a_size',     np.float32, 1),
-                    ('a_dist',     np.float32, 1)])
+                    ('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
-
+    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],
+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],
@@ -64,10 +67,11 @@ cmap = np.array([[255, 124,   0], [255, 163,  76],
                  [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)
+                 [137, 177, 255]], dtype=np.uint8).reshape(1, 24, 3)
 
 
 VERT_SHADER = """
+#version 120
 // Uniforms
 // ------------------------------------
 uniform mat4  u_model;
@@ -96,6 +100,7 @@ void main (void) {
 """
 
 FRAG_SHADER = """
+#version 120
 // Uniforms
 // ------------------------------------
 uniform sampler2D u_colormap;
@@ -108,7 +113,7 @@ 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);
@@ -116,80 +121,70 @@ void main()
 """
 
 
-
 class Canvas(app.Canvas):
 
     def __init__(self):
-        app.Canvas.__init__(self)
+        app.Canvas.__init__(self, keys='interactive')
         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.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)
+        translate(self.view, 0, 0, -self.translate)
 
-        self.timer = app.Timer(1.0/60)
-        self.timer.connect(self.on_timer)
+        self.program.bind(gloo.VertexBuffer(data))
+        self.program['u_colormap'] = gloo.Texture2D(cmap)
+        self.program['u_size'] = 5. / self.translate
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
 
+        self.timer = app.Timer('auto', 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)
+        gloo.set_state(depth_test=False, blend=True,
+                       blend_func=('src_alpha', 'one'), clear_color='black')
         # Start the timer upon initialization.
         self.timer.start()
 
-
-    def on_key_press(self,event):
+    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):
+    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)
+        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 )
+        gloo.set_viewport(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.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.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)
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
 
 if __name__ == '__main__':
     c = Canvas()
diff --git a/examples/demo/gloo/game_of_life.py b/examples/demo/gloo/game_of_life.py
new file mode 100644
index 0000000..92de6d3
--- /dev/null
+++ b/examples/demo/gloo/game_of_life.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 200
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author:   Nicolas P .Rougier
+# Date:     06/03/2014
+# Abstract: GPU computing using the framebuffer
+# Keywords: framebuffer, GPU computing, cellular automata
+# -----------------------------------------------------------------------------
+"""
+Conway game of life.
+"""
+
+import numpy as np
+from vispy.gloo import (Program, FrameBuffer, DepthBuffer, clear, set_viewport,
+                        set_state)
+from vispy import app
+
+
+render_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+render_fragment = """
+uniform int pingpong;
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+void main()
+{
+    float v;
+    v = texture2D(texture, v_texcoord)[pingpong];
+    gl_FragColor = vec4(1.0-v, 1.0-v, 1.0-v, 1.0);
+}
+"""
+
+compute_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+compute_fragment = """
+uniform int pingpong;
+uniform sampler2D texture;
+uniform float dx;          // horizontal distance between texels
+uniform float dy;          // vertical distance between texels
+varying vec2 v_texcoord;
+void main(void)
+{
+    vec2  p = v_texcoord;
+    float old_state, new_state, count;
+
+    old_state = texture2D(texture, p)[pingpong];
+    count = texture2D(texture, p + vec2(-dx,-dy))[pingpong]
+            + texture2D(texture, p + vec2( dx,-dy))[pingpong]
+            + texture2D(texture, p + vec2(-dx, dy))[pingpong]
+            + texture2D(texture, p + vec2( dx, dy))[pingpong]
+            + texture2D(texture, p + vec2(-dx, 0.0))[pingpong]
+            + texture2D(texture, p + vec2( dx, 0.0))[pingpong]
+            + texture2D(texture, p + vec2(0.0,-dy))[pingpong]
+            + texture2D(texture, p + vec2(0.0, dy))[pingpong];
+
+    new_state = old_state;
+    if( old_state > 0.5 ) {
+        // Any live cell with fewer than two live neighbours dies
+        // as if caused by under-population.
+        if( count  < 1.5 )
+            new_state = 0.0;
+
+        // Any live cell with two or three live neighbours
+        // lives on to the next generation.
+
+        // Any live cell with more than three live neighbours dies,
+        //  as if by overcrowding.
+        else if( count > 3.5 )
+            new_state = 0.0;
+    } else {
+        // Any dead cell with exactly three live neighbours becomes
+        //  a live cell, as if by reproduction.
+       if( (count > 2.5) && (count < 3.5) )
+           new_state = 1.0;
+    }
+
+    if( pingpong == 0) {
+        gl_FragColor[1] = new_state;
+        gl_FragColor[0] = old_state;
+    } else {
+        gl_FragColor[1] = old_state;
+        gl_FragColor[0] = new_state;
+    }
+}
+"""
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, title="Conway game of life",
+                            size=(512, 512), keys='interactive')
+        self._timer = app.Timer('auto', connect=self.update, start=True)
+    
+    def on_initialize(self, event):
+        # Build programs
+        # --------------
+        self.comp_size = (512, 512)
+        size = self.comp_size + (4,)
+        Z = np.zeros(size, dtype=np.float32)
+        Z[...] = np.random.randint(0, 2, size)
+        Z[:256, :256, :] = 0
+        gun = """
+        ........................O...........
+        ......................O.O...........
+        ............OO......OO............OO
+        ...........O...O....OO............OO
+        OO........O.....O...OO..............
+        OO........O...O.OO....O.O...........
+        ..........O.....O.......O...........
+        ...........O...O....................
+        ............OO......................"""
+        x, y = 0, 0
+        for i in range(len(gun)):
+            if gun[i] == '\n':
+                y += 1
+                x = 0
+            elif gun[i] == 'O':
+                Z[y, x] = 1
+            x += 1
+
+        self.pingpong = 1
+        self.compute = Program(compute_vertex, compute_fragment, 4)
+        self.compute["texture"] = Z
+        self.compute["position"] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        self.compute["texcoord"] = [(0, 0), (0, 1), (1, 0), (1, 1)]
+        self.compute['dx'] = 1.0 / size[1]
+        self.compute['dy'] = 1.0 / size[0]
+        self.compute['pingpong'] = self.pingpong
+
+        self.render = Program(render_vertex, render_fragment, 4)
+        self.render["position"] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        self.render["texcoord"] = [(0, 0), (0, 1), (1, 0), (1, 1)]
+        self.render["texture"] = self.compute["texture"]
+        self.render['pingpong'] = self.pingpong
+
+        self.fbo = FrameBuffer(self.compute["texture"],
+                               DepthBuffer(self.comp_size))
+        set_state(depth_test=False, clear_color='black')
+
+    def on_draw(self, event):
+        with self.fbo:
+            set_viewport(0, 0, *self.comp_size)
+            self.compute["texture"].interpolation = 'nearest'
+            self.compute.draw('triangle_strip')
+        clear()
+        set_viewport(0, 0, *self.size)
+        self.render["texture"].interpolation = 'linear'
+        self.render.draw('triangle_strip')
+        self.pingpong = 1 - self.pingpong
+        self.compute["pingpong"] = self.pingpong
+        self.render["pingpong"] = self.pingpong
+
+    def on_reshape(self, event):
+        set_viewport(0, 0, *event.size)
+
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/glsl-sandbox-cube.py b/examples/demo/gloo/glsl_sandbox_cube.py
similarity index 66%
rename from examples/glsl-sandbox-cube.py
rename to examples/demo/gloo/glsl_sandbox_cube.py
index 28a77da..47bd363 100644
--- a/examples/glsl-sandbox-cube.py
+++ b/examples/demo/gloo/glsl_sandbox_cube.py
@@ -6,15 +6,17 @@ or PyQt4.
 """
 
 import numpy as np
-from vispy import app, gloo, dataio
+from vispy import app, gloo
+from vispy.io import read_mesh, load_data_file, load_crate
 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 
+# Force using qt and take QtCore+QtGui from backend module
+try:
+    app_object = app.use_app('pyqt4')
+except Exception:
+    app_object = app.use_app('pyside')
+QtCore = app_object.backend_module.QtCore,
+QtGui = app_object.backend_module.QtGui
 
 
 VERT_CODE = """
@@ -32,7 +34,6 @@ 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);
-    
 }
 """
 
@@ -42,7 +43,7 @@ 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));
@@ -51,77 +52,71 @@ void main()
 
 
 # Read cube data
-positions, faces, normals, texcoords = dataio.read_mesh('cube.obj')
-colors = np.random.uniform(0,1,positions.shape).astype('float32')
+positions, faces, normals, texcoords = \
+    read_mesh(load_data_file('orig/cube.obj'))
+colors = np.random.uniform(0, 1, positions.shape).astype('float32')
 
-faces_buffer = gloo.ElementBuffer(faces.astype(np.uint16))
+faces_buffer = gloo.IndexBuffer(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())
-        
+
+        self.program['u_texture'] = gloo.Texture2D(load_crate())
+
         # Handle transformations
         self.init_transforms()
         
-        self.timer = app.Timer(1.0/60)
-        self.timer.connect(self.update_transforms)
-        self.timer.start()
-        
+        self._timer = app.Timer('auto', connect=self.update_transforms)
+        self._timer.start()
     
     def on_initialize(self, event):
-        gl.glClearColor(1,1,1,1)
-        gl.glEnable(gl.GL_DEPTH_TEST)
-    
-    
+        gloo.set_clear_color((1, 1, 1, 1))
+        gloo.set_state(depth_test=True)
+
     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 )
+        gloo.set_viewport(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 on_draw(self, event):
+        gloo.clear()
+        self.program.draw('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.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)
+
+        translate(self.view, 0, 0, -5)
         self.program['u_model'] = self.model
         self.program['u_view'] = self.view
-    
-    
-    def update_transforms(self,event):
+
+    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)
+        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)
@@ -131,30 +126,30 @@ class TextField(QtGui.QPlainTextEdit):
         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)
@@ -168,14 +163,18 @@ class MainWindow(QtGui.QWidget):
         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)
-    
+        self.canvas.program.shaders[0].code = vert_code
+        self.canvas.program.shaders[1].code = frag_code
+        # Because the code has changed, the variables are re-created,
+        # so we need to reset them. This can be considered a bug in gloo
+        # and should be addressed at some point.
+        self.canvas.program['u_projection'] = self.canvas.projection
+        self.canvas.program['u_view'] = self.canvas.view
+        
 
 if __name__ == '__main__':
     app.create()
diff --git a/examples/demo/gloo/graph.py b/examples/demo/gloo/graph.py
new file mode 100644
index 0000000..b216d78
--- /dev/null
+++ b/examples/demo/gloo/graph.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 60
+
+"""
+Dynamic planar graph layout.
+"""
+
+import numpy as np
+from vispy import gloo, app
+from vispy.gloo import set_viewport, set_state, clear
+
+vert = """
+#version 120
+
+// 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 = """
+#version 120
+
+// 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);
+    }
+}
+
+float marker(vec2 P, float size)
+{
+    float r = length((P.xy - vec2(0.5,0.5))*size);
+    r -= v_size/2;
+    return r;
+}
+"""
+
+vs = """
+attribute vec3 a_position;
+attribute vec4 a_fg_color;
+attribute vec4 a_bg_color;
+attribute float a_size;
+attribute float a_linewidth;
+
+void main(){
+    gl_Position = vec4(a_position, 1.);
+}
+"""
+
+fs = """
+void main(){
+    gl_FragColor = vec4(0., 0., 0., 1.);
+}
+"""
+
+n = 100
+ne = 100
+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),
+                          ])
+edges = np.random.randint(size=(ne, 2), low=0, high=n).astype(np.uint32)
+data['a_position'] = np.hstack((.25 * np.random.randn(n, 2), np.zeros((n, 1))))
+data['a_fg_color'] = 0, 0, 0, 1
+color = np.random.uniform(0.5, 1., (n, 3))
+data['a_bg_color'] = np.hstack((color, np.ones((n, 1))))
+data['a_size'] = np.random.randint(size=n, low=10, high=30)
+data['a_linewidth'] = 2
+u_antialias = 1
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self, **kwargs):
+        # Initialize the canvas for real
+        app.Canvas.__init__(self, keys='interactive', **kwargs)
+        self.size = 512, 512
+        self.position = 50, 50
+
+        self.vbo = gloo.VertexBuffer(data)
+        self.index = gloo.IndexBuffer(edges)
+        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 = gloo.Program(vert, frag)
+        self.program.bind(self.vbo)
+        self.program['u_size'] = 1
+        self.program['u_antialias'] = u_antialias
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.program['u_projection'] = self.projection
+
+        self.program_e = gloo.Program(vs, fs)
+        self.program_e.bind(self.vbo)
+
+    def on_initialize(self, event):
+        set_state(clear_color='white', depth_test=False, blend=True,
+                  blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def on_resize(self, event):
+        width, height = event.size
+        set_viewport(0, 0, width, height)
+
+    def on_draw(self, event):
+        clear(color=True, depth=True)
+        self.program_e.draw('lines', self.index)
+        self.program.draw('points')
+
+if __name__ == '__main__':
+    c = Canvas(title="Graph")
+    c.show()
+    app.run()
diff --git a/examples/demo/gloo/grayscott.py b/examples/demo/gloo/grayscott.py
new file mode 100644
index 0000000..1127403
--- /dev/null
+++ b/examples/demo/gloo/grayscott.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 2000
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author:   Nicolas P .Rougier
+# Date:     06/03/2014
+# Abstract: GPU computing usingthe framebuffer
+# Keywords: framebuffer, GPU computing, reaction-diffusion
+# -----------------------------------------------------------------------------
+
+import numpy as np
+from vispy.gloo import (Program, FrameBuffer, DepthBuffer, set_viewport,
+                        clear, set_state)
+from vispy import app
+
+
+render_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+render_fragment = """
+uniform int pingpong;
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+void main()
+{
+    float v;
+    if( pingpong == 0 )
+        v = texture2D(texture, v_texcoord).r;
+    else
+        v = texture2D(texture, v_texcoord).b;
+    gl_FragColor = vec4(1.0-v, 1.0-v, 1.0-v, 1.0);
+}
+"""
+
+compute_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+compute_fragment = """
+uniform int pingpong;
+uniform sampler2D texture; // U,V:= r,g, other channels ignored
+uniform sampler2D params;  // rU,rV,f,k := r,g,b,a
+uniform float dx;          // horizontal distance between texels
+uniform float dy;          // vertical distance between texels
+uniform float dd;          // unit of distance
+uniform float dt;          // unit of time
+varying vec2 v_texcoord;
+void main(void)
+{
+    float center = -(4.0+4.0/sqrt(2.0));  // -1 * other weights
+    float diag   = 1.0/sqrt(2.0);         // weight for diagonals
+    vec2 p = v_texcoord;                  // center coordinates
+
+    vec2 c,l;
+    if( pingpong == 0 ) {
+        c = texture2D(texture, p).rg;    // central value
+        // Compute Laplacian
+        l = ( texture2D(texture, p + vec2(-dx,-dy)).rg
+            + texture2D(texture, p + vec2( dx,-dy)).rg
+            + texture2D(texture, p + vec2(-dx, dy)).rg
+            + texture2D(texture, p + vec2( dx, dy)).rg) * diag
+            + texture2D(texture, p + vec2(-dx, 0.0)).rg
+            + texture2D(texture, p + vec2( dx, 0.0)).rg
+            + texture2D(texture, p + vec2(0.0,-dy)).rg
+            + texture2D(texture, p + vec2(0.0, dy)).rg
+            + c * center;
+    } else {
+        c = texture2D(texture, p).ba;    // central value
+        // Compute Laplacian
+        l = ( texture2D(texture, p + vec2(-dx,-dy)).ba
+            + texture2D(texture, p + vec2( dx,-dy)).ba
+            + texture2D(texture, p + vec2(-dx, dy)).ba
+            + texture2D(texture, p + vec2( dx, dy)).ba) * diag
+            + texture2D(texture, p + vec2(-dx, 0.0)).ba
+            + texture2D(texture, p + vec2( dx, 0.0)).ba
+            + texture2D(texture, p + vec2(0.0,-dy)).ba
+            + texture2D(texture, p + vec2(0.0, dy)).ba
+            + c * center;
+    }
+
+    float u = c.r;           // compute some temporary
+    float v = c.g;           // values which might save
+    float lu = l.r;          // a few GPU cycles
+    float lv = l.g;
+    float uvv = u * v * v;
+
+    vec4 q = texture2D(params, p).rgba;
+    float ru = q.r;          // rate of diffusion of U
+    float rv = q.g;          // rate of diffusion of V
+    float f  = q.b;          // some coupling parameter
+    float k  = q.a;          // another coupling parameter
+
+    float du = ru * lu / dd - uvv + f * (1.0 - u); // Gray-Scott equation
+    float dv = rv * lv / dd + uvv - (f + k) * v;   // diffusion+-reaction
+
+    u += du * dt;
+    v += dv * dt;
+
+    if( pingpong == 1 ) {
+        gl_FragColor = vec4(clamp(u, 0.0, 1.0), clamp(v, 0.0, 1.0), c);
+    } else {
+        gl_FragColor = vec4(c, clamp(u, 0.0, 1.0), clamp(v, 0.0, 1.0));
+    }
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, title='Grayscott Reaction-Diffusion',
+                            size=(512, 512), keys='interactive')
+        self._timer = app.Timer('auto', connect=self.update, start=True)
+    
+    def on_initialize(self, event):
+        self.scale = 4
+        self.comp_size = (256, 256)
+        comp_w, comp_h = self.comp_size
+        dt = 1.0
+        dd = 1.5
+        species = {
+            # name : [r_u, r_v, f, k]
+            'Bacteria 1': [0.16, 0.08, 0.035, 0.065],
+            'Bacteria 2': [0.14, 0.06, 0.035, 0.065],
+            'Coral': [0.16, 0.08, 0.060, 0.062],
+            'Fingerprint': [0.19, 0.05, 0.060, 0.062],
+            'Spirals': [0.10, 0.10, 0.018, 0.050],
+            'Spirals Dense': [0.12, 0.08, 0.020, 0.050],
+            'Spirals Fast': [0.10, 0.16, 0.020, 0.050],
+            'Unstable': [0.16, 0.08, 0.020, 0.055],
+            'Worms 1': [0.16, 0.08, 0.050, 0.065],
+            'Worms 2': [0.16, 0.08, 0.054, 0.063],
+            'Zebrafish': [0.16, 0.08, 0.035, 0.060]
+        }
+        P = np.zeros((comp_h, comp_w, 4), dtype=np.float32)
+        P[:, :] = species['Unstable']
+
+        UV = np.zeros((comp_h, comp_w, 4), dtype=np.float32)
+        UV[:, :, 0] = 1.0
+        r = 32
+        UV[comp_h / 2 - r:comp_h / 2 + r,
+           comp_w / 2 - r:comp_w / 2 + r, 0] = 0.50
+        UV[comp_h / 2 - r:comp_h / 2 + r,
+           comp_w / 2 - r:comp_w / 2 + r, 1] = 0.25
+        UV += np.random.uniform(0.0, 0.01, (comp_h, comp_w, 4))
+        UV[:, :, 2] = UV[:, :, 0]
+        UV[:, :, 3] = UV[:, :, 1]
+
+        self.pingpong = 1
+        self.compute = Program(compute_vertex, compute_fragment, 4)
+        self.compute["params"] = P
+        self.compute["texture"] = UV
+        self.compute["position"] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        self.compute["texcoord"] = [(0, 0), (0, 1), (1, 0), (1, 1)]
+        self.compute['dt'] = dt
+        self.compute['dx'] = 1.0 / comp_w
+        self.compute['dy'] = 1.0 / comp_h
+        self.compute['dd'] = dd
+        self.compute['pingpong'] = self.pingpong
+
+        self.render = Program(render_vertex, render_fragment, 4)
+        self.render["position"] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        self.render["texcoord"] = [(0, 0), (0, 1), (1, 0), (1, 1)]
+        self.render["texture"] = self.compute["texture"]
+        self.render['pingpong'] = self.pingpong
+
+        self.fbo = FrameBuffer(self.compute["texture"],
+                               DepthBuffer(self.comp_size))
+        set_state(depth_test=False, clear_color='black')
+
+    def on_draw(self, event):
+        with self.fbo:
+            set_viewport(0, 0, *self.comp_size)
+            self.compute["texture"].interpolation = 'nearest'
+            self.compute.draw('triangle_strip')
+        clear(color=True)
+        set_viewport(0, 0, *self.size)
+        self.render["texture"].interpolation = 'linear'
+        self.render.draw('triangle_strip')
+        self.pingpong = 1 - self.pingpong
+        self.compute["pingpong"] = self.pingpong
+        self.render["pingpong"] = self.pingpong
+
+    def on_resize(self, event):
+        set_viewport(0, 0, *self.size)
+
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/demo/gloo/imshow.py b/examples/demo/gloo/imshow.py
new file mode 100644
index 0000000..0601f36
--- /dev/null
+++ b/examples/demo/gloo/imshow.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 1
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Show an image using gloo.
+"""
+
+import numpy as np
+from vispy import app
+from vispy.gloo import clear, set_clear_color, set_viewport, Program
+
+
+# Image
+def func(x, y):
+    return (1-x/2+x**5+y**3)*np.exp(-x**2-y**2)
+x = np.linspace(-3.0, 3.0, 512).astype(np.float32)
+y = np.linspace(-3.0, 3.0, 512).astype(np.float32)
+X, Y = np.meshgrid(x, y)
+I = func(X, Y)
+
+# Image normalization
+vmin, vmax = I.min(), I.max()
+I = (I-vmin)/(vmax-vmin)
+
+
+# Colormaps
+colormaps = np.ones((16, 512, 4)).astype(np.float32)
+values = np.linspace(0, 1, 512)[1:-1]
+
+# Hot colormap
+colormaps[0, 0] = 0, 0, 1, 1  # Low values  (< vmin)
+colormaps[0, -1] = 0, 1, 0, 1  # High values (> vmax)
+colormaps[0, 1:-1, 0] = np.interp(values, [0.00, 0.33, 0.66, 1.00],
+                                          [0.00, 1.00, 1.00, 1.00])
+colormaps[0, 1:-1, 1] = np.interp(values, [0.00, 0.33, 0.66, 1.00],
+                                          [0.00, 0.00, 1.00, 1.00])
+colormaps[0, 1:-1, 2] = np.interp(values, [0.00, 0.33, 0.66, 1.00],
+                                          [0.00, 0.00, 0.00, 1.00])
+
+# Grey colormap
+colormaps[1, 0] = 0, 0, 1, 1  # Low values (< vmin)
+colormaps[1, -1] = 0, 1, 0, 1  # High values (> vmax)
+colormaps[1, 1:-1, 0] = np.interp(values, [0.00, 1.00],
+                                          [0.00, 1.00])
+colormaps[1, 1:-1, 1] = np.interp(values, [0.00, 1.00],
+                                          [0.00, 1.00])
+colormaps[1, 1:-1, 2] = np.interp(values, [0.00, 1.00],
+                                          [0.00, 1.00])
+# Jet colormap
+# ...
+
+
+img_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0 );
+    v_texcoord = texcoord;
+}
+"""
+
+img_fragment = """
+uniform float vmin;
+uniform float vmax;
+uniform float cmap;
+
+uniform sampler2D image;
+uniform vec2 image_shape;
+
+uniform sampler2D colormaps;
+uniform vec2 colormaps_shape;
+
+varying vec2 v_texcoord;
+void main()
+{
+    float value = texture2D(image, v_texcoord).r;
+    float index = (cmap+0.5) / colormaps_shape.y;
+
+    if( value < vmin ) {
+        gl_FragColor = texture2D(colormaps, vec2(0.0,index));
+    } else if( value > vmax ) {
+        gl_FragColor = texture2D(colormaps, vec2(1.0,index));
+    } else {
+        value = (value-vmin)/(vmax-vmin);
+        value = 1.0/512.0 + 510.0/512.0*value;
+        gl_FragColor = texture2D(colormaps, vec2(value,index));
+    }
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        self.image = Program(img_vertex, img_fragment, 4)
+        self.image['position'] = (-1, -1), (-1, +1), (+1, -1), (+1, +1)
+        self.image['texcoord'] = (0, 0), (0, +1), (+1, 0), (+1, +1)
+        self.image['vmin'] = +0.1
+        self.image['vmax'] = +0.9
+        self.image['cmap'] = 0  # Colormap index to use
+
+        self.image['colormaps'] = colormaps
+        self.image['colormaps'].interpolation = 'linear'
+        self.image['colormaps_shape'] = colormaps.shape[1], colormaps.shape[0]
+
+        self.image['image'] = I
+        self.image['image'].interpolation = 'linear'
+        self.image['image_shape'] = I.shape[1], I.shape[0]
+        app.Canvas.__init__(self, show=True, size=(512, 512),
+                            keys='interactive')
+
+    def on_initialize(self, event):
+        set_clear_color('black')
+
+    def on_resize(self, event):
+        width, height = event.size
+        set_viewport(0, 0, *event.size)
+
+    def on_draw(self, event):
+        clear(color=True, depth=True)
+        self.image.draw('triangle_strip')
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    app.run()
diff --git a/examples/demo/gloo/imshow_cuts.py b/examples/demo/gloo/imshow_cuts.py
new file mode 100644
index 0000000..893914b
--- /dev/null
+++ b/examples/demo/gloo/imshow_cuts.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Show an image using gloo, with on-mouseover cross-section visualizations.
+"""
+
+import numpy as np
+from vispy import app
+from vispy.gloo import set_viewport, clear, set_state, Program
+
+
+# Image
+def func(x, y):
+    return (1-x/2+x**5+y**3)*np.exp(-x**2-y**2)
+x = np.linspace(-3.0, 3.0, 512).astype(np.float32)
+y = np.linspace(-3.0, 3.0, 512).astype(np.float32)
+X, Y = np.meshgrid(x, y)
+I = func(X, Y)
+
+# Image normalization
+vmin, vmax = I.min(), I.max()
+I = (I-vmin)/(vmax-vmin)
+
+
+# Colormaps
+colormaps = np.ones((16, 512, 4)).astype(np.float32)
+values = np.linspace(0, 1, 512)[1:-1]
+
+# Hot colormap
+colormaps[0, 0] = 0, 0, 1, 1  # Low values  (< vmin)
+colormaps[0, -1] = 0, 1, 0, 1  # High values (> vmax)
+colormaps[0, 1:-1, 0] = np.interp(values, [0.00, 0.33, 0.66, 1.00],
+                                          [0.00, 1.00, 1.00, 1.00])
+colormaps[0, 1:-1, 1] = np.interp(values, [0.00, 0.33, 0.66, 1.00],
+                                          [0.00, 0.00, 1.00, 1.00])
+colormaps[0, 1:-1, 2] = np.interp(values, [0.00, 0.33, 0.66, 1.00],
+                                          [0.00, 0.00, 0.00, 1.00])
+
+# Grey colormap
+colormaps[1, 0] = 0, 0, 1, 1  # Low values (< vmin)
+colormaps[1, -1] = 0, 1, 0, 1  # High values (> vmax)
+colormaps[1, 1:-1, 0] = np.interp(values, [0.00, 1.00],
+                                          [0.00, 1.00])
+colormaps[1, 1:-1, 1] = np.interp(values, [0.00, 1.00],
+                                          [0.00, 1.00])
+colormaps[1, 1:-1, 2] = np.interp(values, [0.00, 1.00],
+                                          [0.00, 1.00])
+# Jet colormap
+# ...
+
+
+lines_vertex = """
+attribute vec2 position;
+attribute vec4 color;
+varying vec4 v_color;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0 );
+    v_color = color;
+}
+"""
+
+lines_fragment = """
+varying vec4 v_color;
+void main()
+{
+    gl_FragColor = v_color;
+}
+"""
+
+
+image_vertex = """
+attribute vec2 position;
+attribute vec2 texcoord;
+
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = vec4(position, 0.0, 1.0 );
+    v_texcoord = texcoord;
+}
+"""
+
+image_fragment = """
+uniform float vmin;
+uniform float vmax;
+uniform float cmap;
+uniform float n_colormaps;
+
+uniform sampler2D image;
+uniform sampler2D colormaps;
+
+varying vec2 v_texcoord;
+void main()
+{
+    float value = texture2D(image, v_texcoord).r;
+    float index = (cmap+0.5) / n_colormaps;
+
+    if( value < vmin ) {
+        gl_FragColor = texture2D(colormaps, vec2(0.0,index));
+    } else if( value > vmax ) {
+        gl_FragColor = texture2D(colormaps, vec2(1.0,index));
+    } else {
+        value = (value-vmin)/(vmax-vmin);
+        value = 1.0/512.0 + 510.0/512.0*value;
+        gl_FragColor = texture2D(colormaps, vec2(value,index));
+    }
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+
+        self.image = Program(image_vertex, image_fragment, 4)
+        self.image['position'] = (-1, -1), (-1, +1), (+1, -1), (+1, +1)
+        self.image['texcoord'] = (0, 0), (0, +1), (+1, 0), (+1, +1)
+        self.image['vmin'] = +0.0
+        self.image['vmax'] = +1.0
+        self.image['cmap'] = 0  # Colormap index to use
+        self.image['colormaps'] = colormaps
+        self.image['n_colormaps'] = colormaps.shape[0]
+        self.image['image'] = I
+        self.image['image'].interpolation = 'linear'
+
+        self.lines = Program(lines_vertex, lines_fragment, 4+4+514+514)
+        self.lines["position"] = np.zeros((4+4+514+514, 2))
+        color = np.zeros((4+4+514+514, 4))
+        color[1:1+2, 3] = 0.25
+        color[5:5+2, 3] = 0.25
+        color[9:9+512, 3] = 0.5
+        color[523:523+512, 3] = 0.5
+        self.lines["color"] = color
+        app.Canvas.__init__(self, show=True, size=(512, 512),
+                            keys='interactive')
+
+    def on_initialize(self, event):
+        set_state(clear_color='white', blend=True,
+                  blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def on_resize(self, event):
+        set_viewport(0, 0, *event.size)
+
+    def on_draw(self, event):
+        clear(color=True, depth=True)
+        self.image.draw('triangle_strip')
+        self.lines.draw('line_strip')
+
+    def on_mouse_move(self, event):
+        x, y = event.pos
+        w, h = self.size
+        yf = 1 - y/(h/2.)
+        xf = x/(w/2.) - 1
+        P = np.zeros((4+4+514+514, 2), np.float32)
+
+        x_baseline = P[:4]
+        y_baseline = P[4:8]
+        x_profile = P[8:522]
+        y_profile = P[522:]
+
+        x_baseline[...] = (-1, yf), (-1, yf), (1, yf), (1, yf)
+        y_baseline[...] = (xf, -1), (xf, -1), (xf, 1), (xf, 1)
+
+        x_profile[1:-1, 0] = np.linspace(-1, 1, 512)
+        x_profile[1:-1, 1] = yf+0.15*I[y]
+        x_profile[0] = x_profile[1]
+        x_profile[-1] = x_profile[-2]
+
+        y_profile[1:-1, 0] = xf+0.15*I[:, x]
+        y_profile[1:-1, 1] = np.linspace(-1, 1, 512)
+        y_profile[0] = y_profile[1]
+        y_profile[-1] = y_profile[-2]
+
+        self.lines["position"] = P
+        self.update()
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    app.run()
diff --git a/examples/demo/gloo/jfa/fragment_display.glsl b/examples/demo/gloo/jfa/fragment_display.glsl
new file mode 100755
index 0000000..22fd516
--- /dev/null
+++ b/examples/demo/gloo/jfa/fragment_display.glsl
@@ -0,0 +1,23 @@
+// Jump flooding algorithm for EDT according
+// to Danielsson (1980) and Guodong Rong (2007).
+// Implementation by Stefan Gustavson 2010.
+// This code is in the public domain.
+
+// This shader displays the final distance field
+// visualized as an RGB image.
+
+uniform sampler2D texture;
+varying vec2 uv;
+
+vec2 remap(vec4 floatdata) {
+    vec2 scaled_data = vec2(floatdata.x * 65280. + floatdata.z * 255.,
+                            floatdata.y * 65280. + floatdata.w * 255.);
+    return scaled_data / 32768. - 1.0;
+}
+
+void main( void )
+{
+  vec2 distvec = remap(texture2D(texture, uv).rgba);
+  vec2 rainbow = 0.5+0.5*(normalize(distvec));
+  gl_FragColor = vec4(rainbow, 1.0-length(distvec)*4.0, 1.0);
+}
diff --git a/examples/demo/gloo/jfa/fragment_flood.glsl b/examples/demo/gloo/jfa/fragment_flood.glsl
new file mode 100755
index 0000000..c4b4d30
--- /dev/null
+++ b/examples/demo/gloo/jfa/fragment_flood.glsl
@@ -0,0 +1,159 @@
+// Jump flooding algorithm for EDT according
+// to Danielsson (1980) and Guodong Rong (2007).
+// Implementation by Stefan Gustavson 2010.
+// This code is in the public domain.
+
+// This code represents one iteration of the flood filling.
+// You need to run it multiple times with different step
+// lengths to perform a full distance transformation.
+
+uniform sampler2D texture;
+varying float stepu;
+varying float stepv;
+varying vec2 uv;
+
+// Helper functions to remap unsigned normalized floats [0.0,1.0]
+// coming from an integer texture to the range we need [-1, 1].
+// The transformations are very specifically designed to map
+// integer texel values exactly to pixel centers, and vice versa.
+// (See fragment_seed.glsl for details.)
+
+vec2 remap(vec4 floatdata) {
+    vec2 scaleddata = vec2(floatdata.x * 65280. + floatdata.z * 255.,
+                           floatdata.y * 65280. + floatdata.w * 255.);
+    return scaleddata / 32768. - 1.0;
+}
+
+vec4 remap_inv(vec2 floatvec) {
+    vec2 data = (floatvec + 1.0) * 32768.;
+    float x = floor(data.x / 256.);
+    float y = floor(data.y / 256.);
+    return vec4(x, y, data.x - x * 256., data.y - y * 256.) / 255.;
+}
+
+void main( void )
+{
+  // Search for better distance vectors among 8 candidates
+  vec2 stepvec; // Relative offset to candidate being tested
+  vec2 newvec;  // Absolute position of that candidate
+  vec3 newseed; // Closest point from that candidate (.xy) and its distance (.z)
+  vec3 bestseed; // Closest seed so far
+  bestseed.xy = remap(texture2D(texture, uv).rgba);
+  bestseed.z = length(bestseed.xy);
+
+  // This code depends on the texture having a CLAMP_TO_BORDER
+  // attribute and a border color with R = 0.
+  // The commented-out lines handle clamping to the edge explicitly
+  // to avoid propagating incorrect vectors when looking outside
+  // of [0,1] in u and/or v.
+  // These explicit conditionals cause a slowdown of about 25%.
+  // Sometimes a periodic transform with edge repeats might be
+  // what you want. In that case, the texture wrap mode can be
+  // set to GL_REPEAT, and the shader code can be left unchanged.
+
+  stepvec = vec2(-stepu, -stepv);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(-stepu, 0.0);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(-stepu, stepv);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(0.0, -stepv);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(0.0, stepv);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(stepu, -stepv);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(stepu, 0.0);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(stepu, stepv);
+  newvec = uv + stepvec;
+  if ( all( bvec4( lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0)) ) ) ) {
+    newseed.xy = remap(texture2D(texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate distance"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  gl_FragColor = remap_inv(bestseed.xy);
+}
diff --git a/examples/demo/gloo/jfa/fragment_seed.glsl b/examples/demo/gloo/jfa/fragment_seed.glsl
new file mode 100755
index 0000000..1affab1
--- /dev/null
+++ b/examples/demo/gloo/jfa/fragment_seed.glsl
@@ -0,0 +1,21 @@
+// Jump flooding algorithm for EDT according
+// to Danielsson (1980) and Guodong Rong (2007).
+// Implementation by Stefan Gustavson 2010.
+// This code is in the public domain.
+
+// This shader initializes the distance field
+// in preparation for the flood filling.
+
+uniform sampler2D texture;
+varying float stepu;
+varying float stepv;
+varying vec2 uv;
+
+void main( void )
+{
+  float pixel = texture2D(texture, uv).r;
+  vec4 myzero = vec4(128. / 255., 128. / 255., 0., 0.);  // Zero
+  vec4 myinfinity = vec4(0., 0., 0., 0.);                // Infinity
+  // Pixels > 0.5 are objects, others are background
+  gl_FragColor = pixel > 0.5 ? myinfinity : myzero;
+}
diff --git a/examples/demo/gloo/jfa/jfa_translation.py b/examples/demo/gloo/jfa/jfa_translation.py
new file mode 100644
index 0000000..86bf539
--- /dev/null
+++ b/examples/demo/gloo/jfa/jfa_translation.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+"""
+Demo of jump flooding algoritm for EDT using GLSL
+Author: Stefan Gustavson (stefan.gustavson at gmail.com)
+2010-08-24. This code is in the public domain.
+
+Adapted to `vispy` by Eric Larson <larson.eric.d at gmail.com>.
+
+This version is a translation of the OSX C code to Python.
+Two modifications were made for OpenGL ES 2.0 compatibility:
+
+    1. GL_CLAMP_TO_BORDER was changed to GL_CLAMP_TO_EDGE, with
+       corresponding shader changes.
+    2. GL_RG16 was changed to GL_RGBA with corresponding shader changes
+       (including hard-coding "texlevels" at 65536).
+
+"""
+
+import numpy as np
+from os import path as op
+from vispy.ext import glfw
+from vispy.io import load_data_file
+from OpenGL import GL as gl
+from OpenGL import GLU as glu
+from PIL import Image
+import time
+
+this_dir = op.abspath(op.dirname(__file__))
+
+
+def createShader(vert_fname, frag_fname):
+    """createShader - create, load, compile and link the shader object"""
+    with open(op.join(this_dir, vert_fname), 'rb') as fid:
+        vert = fid.read().decode('ASCII')
+    with open(op.join(this_dir, frag_fname), 'rb') as fid:
+        frag = fid.read().decode('ASCII')
+    vertexShader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+    gl.glShaderSource(vertexShader, vert)
+    gl.glCompileShader(vertexShader)
+    fragmentShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+    gl.glShaderSource(fragmentShader, frag)
+    gl.glCompileShader(fragmentShader)
+    programObj = gl.glCreateProgram()
+    gl.glAttachShader(programObj, vertexShader)
+    gl.glAttachShader(programObj, fragmentShader)
+    gl.glLinkProgram(programObj)
+    checkGLError()
+    return programObj
+
+
+def setUniformVariables(programObj, texture, texw, texh, step):
+    """setUniformVariables - set the uniform shader variables we need"""
+    gl.glUseProgram(programObj)
+    location_texture = gl.glGetUniformLocation(programObj, "texture")
+    if location_texture != -1:
+        gl.glUniform1i(location_texture, texture)
+    location_texw = gl.glGetUniformLocation(programObj, "texw")
+    if location_texw != -1:
+        gl.glUniform1f(location_texw, texw)
+    location_texh = gl.glGetUniformLocation(programObj, "texh")
+    if location_texh != -1:
+        gl.glUniform1f(location_texh, texh)
+    location_step = gl.glGetUniformLocation(programObj, "step")
+    if(location_step != -1):
+        gl.glUniform1f(location_step, step)
+    gl.glUseProgram(0)
+    checkGLError()
+
+
+def loadImage(filename):  # adapted for Python
+    img = Image.open(filename)
+    w, h = img.size
+    x = np.array(img)[::-1].tostring()
+    assert len(x) == w * h
+    return x, w, h
+
+
+def loadShapeTexture(filename, texID):
+    """loadShapeTexture - load 8-bit shape texture data
+    from a TGA file and set up the corresponding texture object."""
+    data, texw, texh = loadImage(load_data_file('jfa/' + filename))
+    gl.glActiveTexture(gl.GL_TEXTURE0)
+    gl.glBindTexture(gl.GL_TEXTURE_2D, texID)
+    # Load image into texture
+    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_LUMINANCE, texw, texh, 0,
+                    gl.GL_LUMINANCE, gl.GL_UNSIGNED_BYTE, data)
+    # This is the input image. We want unaltered 1-to-1 pixel values,
+    # so specify nearest neighbor sampling to be sure.
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER,
+                       gl.GL_NEAREST)
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER,
+                       gl.GL_NEAREST)
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_REPEAT)
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_REPEAT)
+    checkGLError()
+    return texw, texh
+
+
+def createBufferTexture(texID, texw, texh):
+    """createBufferTexture - create an 8-bit texture render target"""
+    gl.glActiveTexture(gl.GL_TEXTURE0)
+    gl.glBindTexture(gl.GL_TEXTURE_2D, texID)
+    black = (0., 0., 0., 0.)
+    # The special shader used to render this texture performs a
+    # per-pixel image processing where point sampling is required,
+    # so specify nearest neighbor sampling.
+    #
+    # Also, the flood fill shader handles its own edge clamping, so
+    # texture mode GL_REPEAT is inconsequential. "Zero outside" would
+    # be useful, but separate edge values are deprecated in OpenGL.
+    #
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER,
+                       gl.GL_NEAREST)
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER,
+                       gl.GL_NEAREST)
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S,
+                       gl.GL_CLAMP_TO_EDGE)
+    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T,
+                       gl.GL_CLAMP_TO_EDGE)
+    gl.glTexParameterfv(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_BORDER_COLOR, black)
+    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, texw, texh, 0,
+                    gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, '\x00' * texw*texh*4)
+    gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
+    checkGLError()
+
+
+t0 = 0.0
+frames = 0
+
+
+def showFPS(texw, texh):
+    """showFPS - Calculate and report texture size and frames per second
+    in the window title bar (updated once per second)"""
+    global frames, t0
+    t = time.time()
+    if (t - t0) > 1.:
+        fps = frames / (t - t0)
+        titlestr = "%sx%s texture, %.1f FPS" % (texw, texh, fps)
+        glfw.glfwSetWindowTitle(window, titlestr)
+        t0 = t
+        frames = 0
+    frames += 1
+
+
+def checkGLError():
+    status = gl.glGetError()
+    if status != gl.GL_NO_ERROR:
+        raise RuntimeError('gl error %s' % (status,))
+
+
+def renderScene(programObj, width, height):
+    """renderScene - the OpenGL commands to render our scene."""
+    gl.glMatrixMode(gl.GL_PROJECTION)
+    gl.glLoadIdentity()
+    glu.gluOrtho2D(0, width, 0, height)
+    gl.glViewport(0, 0, width, height)
+    gl.glMatrixMode(gl.GL_MODELVIEW)
+    gl.glLoadIdentity()
+    gl.glUseProgram(programObj)
+    # Draw one texture mapped quad in the (x,y) plane
+    gl.glBegin(gl.GL_QUADS)
+    gl.glTexCoord2f(0., 0.)
+    gl.glVertex2f(0., 0.)
+    gl.glTexCoord2f(1., 0.)
+    gl.glVertex2f(float(width), 0.)
+    gl.glTexCoord2f(1., 1.)
+    gl.glVertex2f(float(width), float(height))
+    gl.glTexCoord2f(0., 1.)
+    gl.glVertex2f(0., float(height))
+    gl.glEnd()
+    gl.glUseProgram(0)
+    checkGLError()
+
+
+useShaders = True
+glfw.glfwInit()
+window = glfw.glfwCreateWindow(512, 512)
+glfw.glfwShowWindow(window)
+glfw.glfwMakeContextCurrent(window)
+time.sleep(400e-3)  # needed on Linux for window to show up
+
+# Load one texture with the original image
+# and create two textures of the same size for the iterative rendering
+gl.glEnable(gl.GL_TEXTURE_2D)
+gl.glActiveTexture(gl.GL_TEXTURE0)
+textureID = gl.glGenTextures(3)
+texw, texh = loadShapeTexture("shape1.tga", textureID[0])
+createBufferTexture(textureID[1], texw, texh)
+createBufferTexture(textureID[2], texw, texh)
+fboID = gl.glGenFramebuffers(1)
+programObj0 = createShader("vertex.glsl", "fragment_seed.glsl")
+programObj1 = createShader("vertex.glsl", "fragment_flood.glsl")
+programObj2 = createShader("vertex.glsl", "fragment_display.glsl")
+glfw.glfwSwapInterval(0)
+running = True
+while running:
+    showFPS(texw, texh)
+    if not useShaders:
+        gl.glBindTexture(gl.GL_TEXTURE_2D, textureID[0])  # Pass-through
+    else:
+        setUniformVariables(programObj0, 0, texw, texh, 0)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, textureID[0])
+        gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, fboID)
+        lastRendered = 1
+        gl.glFramebufferTexture2D(gl.GL_DRAW_FRAMEBUFFER,
+                                  gl.GL_COLOR_ATTACHMENT0,
+                                  gl.GL_TEXTURE_2D,
+                                  textureID[lastRendered], 0)
+        renderScene(programObj0, texw, texh)
+        stepsize = texw//2 if texw > texh else texh//2
+        while stepsize > 0:
+            setUniformVariables(programObj1, 0, texw, texh, stepsize)
+            gl.glBindTexture(gl.GL_TEXTURE_2D, textureID[lastRendered])
+            lastRendered = 1 if lastRendered == 2 else 2
+            gl.glFramebufferTexture2D(gl.GL_DRAW_FRAMEBUFFER,
+                                      gl.GL_COLOR_ATTACHMENT0,
+                                      gl.GL_TEXTURE_2D,
+                                      textureID[lastRendered], 0)
+            renderScene(programObj1, texw, texh)
+            stepsize = stepsize // 2
+        gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, 0)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, textureID[lastRendered])
+
+    width, height = glfw.glfwGetWindowSize(window)
+    height = max(height, 1)
+    width = max(width, 1)
+    setUniformVariables(programObj2, 0, texw, texh, 0)
+    renderScene(programObj2, width, height)
+
+    glfw.glfwSwapBuffers(window)
+    glfw.glfwPollEvents()
+    if glfw.glfwGetKey(window, glfw.GLFW_KEY_1) == glfw.GLFW_PRESS:
+        texw, texh = loadShapeTexture("shape1.tga", textureID[0])
+        createBufferTexture(textureID[1], texw, texh)
+        createBufferTexture(textureID[2], texw, texh)
+    if glfw.glfwGetKey(window, glfw.GLFW_KEY_2):
+        texw, texh = loadShapeTexture("shape2.tga", textureID[0])
+        createBufferTexture(textureID[1], texw, texh)
+        createBufferTexture(textureID[2], texw, texh)
+    if glfw.glfwGetKey(window, glfw.GLFW_KEY_3):
+        texw, texh = loadShapeTexture("shape3.tga", textureID[0])
+        createBufferTexture(textureID[1], texw, texh)
+        createBufferTexture(textureID[2], texw, texh)
+    if glfw.glfwGetKey(window, glfw.GLFW_KEY_4):
+        texw, texh = loadShapeTexture("shape4.tga", textureID[0])
+        createBufferTexture(textureID[1], texw, texh)
+        createBufferTexture(textureID[2], texw, texh)
+    if glfw.glfwGetKey(window, glfw.GLFW_KEY_F1):
+        useShaders = True
+    if glfw.glfwGetKey(window, glfw.GLFW_KEY_F2):
+        useShaders = False
+    # Check if the ESC key is pressed or the window has been closed
+    running = not glfw.glfwGetKey(window, glfw.GLFW_KEY_ESCAPE)
+glfw.glfwTerminate()
diff --git a/examples/demo/gloo/jfa/jfa_vispy.py b/examples/demo/gloo/jfa/jfa_vispy.py
new file mode 100644
index 0000000..b9b7c0c
--- /dev/null
+++ b/examples/demo/gloo/jfa/jfa_vispy.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+
+"""
+Demo of jump flooding algoritm for EDT using GLSL
+Author: Stefan Gustavson (stefan.gustavson at gmail.com)
+2010-08-24. This code is in the public domain.
+
+Adapted to `vispy` by Eric Larson <larson.eric.d at gmail.com>.
+
+This version is a vispy-ized translation of jfa_translate.py.
+"""
+
+import numpy as np
+from os import path as op
+from PIL import Image
+from vispy import app
+from vispy.gloo import (Program, VertexShader, FragmentShader, FrameBuffer,
+                        VertexBuffer, Texture2D, set_viewport)
+from vispy.io import load_data_file
+
+this_dir = op.abspath(op.dirname(__file__))
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        self.use_shaders = True
+        app.Canvas.__init__(self, size=(512, 512), keys='interactive')
+        self._timer = app.Timer('auto', self.update, start=True)
+
+    def _setup_textures(self, fname):
+        img = Image.open(load_data_file('jfa/' + fname))
+        self.texture_size = tuple(img.size)
+        data = np.array(img, np.ubyte)[::-1].copy()
+        self.orig_tex = Texture2D(data, format='luminance')
+        self.orig_tex.wrapping = 'repeat'
+        self.orig_tex.interpolation = 'nearest'
+
+        self.comp_texs = []
+        data = np.zeros(self.texture_size + (4,), np.float32)
+        for _ in range(2):
+            tex = Texture2D(data, format='rgba')
+            tex.interpolation = 'nearest'
+            tex.wrapping = 'clamp_to_edge'
+            self.comp_texs.append(tex)
+        self.fbo_to[0].color_buffer = self.comp_texs[0]
+        self.fbo_to[1].color_buffer = self.comp_texs[1]
+        for program in self.programs:
+            program['texw'], program['texh'] = self.texture_size
+
+    def on_initialize(self, event):
+        with open(op.join(this_dir, 'vertex_vispy.glsl'), 'rb') as fid:
+            vert = VertexShader(fid.read().decode('ASCII'))
+        with open(op.join(this_dir, 'fragment_seed.glsl'), 'rb') as f:
+            frag_seed = FragmentShader(f.read().decode('ASCII'))
+        with open(op.join(this_dir, 'fragment_flood.glsl'), 'rb') as f:
+            frag_flood = FragmentShader(f.read().decode('ASCII'))
+        with open(op.join(this_dir, 'fragment_display.glsl'), 'rb') as f:
+            frag_display = FragmentShader(f.read().decode('ASCII'))
+        self.programs = [Program(vert, frag_seed),
+                         Program(vert, frag_flood),
+                         Program(vert, frag_display)]
+        # Initialize variables
+        # using two FBs slightly faster than switching on one
+        self.fbo_to = [FrameBuffer(), FrameBuffer()]
+        self._setup_textures('shape1.tga')
+        vtype = np.dtype([('position', 'f4', 2), ('texcoord', 'f4', 2)])
+        vertices = np.zeros(4, dtype=vtype)
+        vertices['position'] = [[-1., -1.], [-1., 1.], [1., -1.], [1., 1.]]
+        vertices['texcoord'] = [[0., 0.], [0., 1.], [1., 0.], [1., 1.]]
+        vertices = VertexBuffer(vertices)
+        for program in self.programs:
+            program['step'] = 0
+            program.bind(vertices)
+
+    def on_draw(self, event):
+        if self.use_shaders:
+            last_rend = 0
+            self.fbo_to[last_rend].activate()
+            set_viewport(0, 0, *self.texture_size)
+            self.programs[0]['texture'] = self.orig_tex
+            self.programs[0].draw('triangle_strip')
+            self.fbo_to[last_rend].deactivate()
+            stepsize = (np.array(self.texture_size) // 2).max()
+            while stepsize > 0:
+                self.programs[1]['step'] = stepsize
+                self.programs[1]['texture'] = self.comp_texs[last_rend]
+                last_rend = 1 if last_rend == 0 else 0
+                self.fbo_to[last_rend].activate()
+                set_viewport(0, 0, *self.texture_size)
+                self.programs[1].draw('triangle_strip')
+                self.fbo_to[last_rend].deactivate()
+                stepsize //= 2
+            self.programs[2]['texture'] = self.comp_texs[last_rend]
+        else:
+            self.programs[2]['texture'] = self.orig_tex
+        set_viewport(0, 0, *self.size)
+        self.programs[2].draw('triangle_strip')
+
+    def on_key_press(self, event):
+        if event.key is not None and event.key.name in '1234':
+            fname = "shape%s.tga" % event.key.name
+            self._setup_textures(fname)
+        elif event.key == 'F1':
+            self.use_shaders = True
+        elif event.key == 'F2':
+            self.use_shaders = False
+
+
+def fun(x):
+    c.title = 'FPS: %0.1f' % x
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    c.measure_fps(callback=fun)
+    c.app.run()
diff --git a/examples/demo/gloo/jfa/vertex.glsl b/examples/demo/gloo/jfa/vertex.glsl
new file mode 100755
index 0000000..8592020
--- /dev/null
+++ b/examples/demo/gloo/jfa/vertex.glsl
@@ -0,0 +1,24 @@
+// Jump flooding algorithm for EDT according
+// to Danielsson (1980) and Guodong Rong (2007).
+// Implementation by Stefan Gustavson 2010.
+// This code is in the public domain.
+
+// This code represents one iteration of the flood filling.
+// You need to run it multiple times with different step
+// lengths to perform a full distance transformation.
+
+uniform float texw;
+uniform float texh;
+uniform float step;
+varying float stepu;
+varying float stepv;
+varying vec2 uv;
+
+void main( void )
+{
+  // Get the texture coordinates
+  uv = gl_MultiTexCoord0.xy;
+  stepu = step / texw; // Saves a division in the fragment shader
+  stepv = step / texh;
+  gl_Position = ftransform();
+}
diff --git a/examples/demo/gloo/jfa/vertex_vispy.glsl b/examples/demo/gloo/jfa/vertex_vispy.glsl
new file mode 100755
index 0000000..85c59d1
--- /dev/null
+++ b/examples/demo/gloo/jfa/vertex_vispy.glsl
@@ -0,0 +1,26 @@
+// Jump flooding algorithm for EDT according
+// to Danielsson (1980) and Guodong Rong (2007).
+// Implementation by Stefan Gustavson 2010.
+// This code is in the public domain.
+
+// This code represents one iteration of the flood filling.
+// You need to run it multiple times with different step
+// lengths to perform a full distance transformation.
+
+uniform float texw;
+uniform float texh;
+uniform float step;
+attribute vec2 position;
+attribute vec2 texcoord;
+varying float stepu;
+varying float stepv;
+varying vec2 uv;
+
+void main( void )
+{
+  // Get the texture coordinates
+  uv = texcoord.xy;
+  stepu = step / texw; // Saves a division in the fragment shader
+  stepv = step / texh;
+  gl_Position = vec4(position.xy, 0., 1.);
+}
diff --git a/examples/demo/gloo/mandelbrot.py b/examples/demo/gloo/mandelbrot.py
new file mode 100644
index 0000000..c34127d
--- /dev/null
+++ b/examples/demo/gloo/mandelbrot.py
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: John David Reaver
+# Date:   04/29/2014
+# -----------------------------------------------------------------------------
+
+from vispy import app, gloo
+
+# Shader source code
+# -----------------------------------------------------------------------------
+vertex = """
+attribute vec2 position;
+
+void main()
+{
+    gl_Position = vec4(position, 0, 1.0);
+}
+"""
+
+fragment = """
+uniform vec2 resolution;
+uniform vec2 center;
+uniform float scale;
+uniform int iter;
+
+// Jet color scheme
+vec4 color_scheme(float x) {
+    vec3 a, b;
+    float c;
+    if (x < 0.34) {
+        a = vec3(0, 0, 0.5);
+        b = vec3(0, 0.8, 0.95);
+        c = (x - 0.0) / (0.34 - 0.0);
+    } else if (x < 0.64) {
+        a = vec3(0, 0.8, 0.95);
+        b = vec3(0.85, 1, 0.04);
+        c = (x - 0.34) / (0.64 - 0.34);
+    } else if (x < 0.89) {
+        a = vec3(0.85, 1, 0.04);
+        b = vec3(0.96, 0.7, 0);
+        c = (x - 0.64) / (0.89 - 0.64);
+    } else {
+        a = vec3(0.96, 0.7, 0);
+        b = vec3(0.5, 0, 0);
+        c = (x - 0.89) / (1.0 - 0.89);
+    }
+    return vec4(mix(a, b, c), 1.0);
+}
+
+void main() {
+    vec2 z, c;
+
+    // Recover coordinates from pixel coordinates
+    c.x = (gl_FragCoord.x / resolution.x - 0.5) * scale + center.x;
+    c.y = (gl_FragCoord.y / resolution.y - 0.5) * scale + center.y;
+
+    // Main Mandelbrot computation
+    int i;
+    z = c;
+    for(i = 0; i < iter; i++) {
+        float x = (z.x * z.x - z.y * z.y) + c.x;
+        float y = (z.y * z.x + z.x * z.y) + c.y;
+
+        if((x * x + y * y) > 4.0) break;
+        z.x = x;
+        z.y = y;
+    }
+
+    // Convert iterations to color
+    float color = 1.0 - float(i) / float(iter);
+    gl_FragColor = color_scheme(color);
+
+}
+"""
+
+
+# vispy Canvas
+# -----------------------------------------------------------------------------
+class Canvas(app.Canvas):
+
+    def __init__(self, *args, **kwargs):
+        app.Canvas.__init__(self, *args, **kwargs)
+        self.program = gloo.Program(vertex, fragment)
+
+        # Draw a rectangle that takes up the whole screen. All of the work is
+        # done in the shader.
+        self.program["position"] = [(-1, -1), (-1, 1), (1, 1),
+                                    (-1, -1), (1, 1), (1, -1)]
+
+        self.scale = self.program["scale"] = 3
+        self.center = self.program["center"] = [-0.5, 0]
+        self.iterations = self.program["iter"] = 300
+        self.program['resolution'] = self.size
+
+        self.bounds = [-2, 2]
+        self.min_scale = 0.00005
+        self.max_scale = 4
+
+        self._timer = app.Timer('auto', connect=self.update, start=True)
+
+    def on_initialize(self, event):
+        gloo.set_clear_color(color='black')
+
+    def on_draw(self, event):
+        self.program.draw()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        self.program['resolution'] = [width, height]
+
+    def on_mouse_move(self, event):
+        """Pan the view based on the change in mouse position."""
+        if event.is_dragging and event.buttons[0] == 1:
+            x0, y0 = event.last_event.pos[0], event.last_event.pos[1]
+            x1, y1 = event.pos[0], event.pos[1]
+            X0, Y0 = self.pixel_to_coords(float(x0), float(y0))
+            X1, Y1 = self.pixel_to_coords(float(x1), float(y1))
+            self.translate_center(X1 - X0, Y1 - Y0)
+
+    def translate_center(self, dx, dy):
+        """Translates the center point, and keeps it in bounds."""
+        center = self.center
+        center[0] -= dx
+        center[1] -= dy
+        center[0] = min(max(center[0], self.bounds[0]), self.bounds[1])
+        center[1] = min(max(center[1], self.bounds[0]), self.bounds[1])
+        self.program["center"] = self.center = center
+
+    def pixel_to_coords(self, x, y):
+        """Convert pixel coordinates to Mandelbrot set coordinates."""
+        rx, ry = self.size
+        nx = (x / rx - 0.5) * self.scale + self.center[0]
+        ny = ((ry - y) / ry - 0.5) * self.scale + self.center[1]
+        return [nx, ny]
+
+    def on_mouse_wheel(self, event):
+        """Use the mouse wheel to zoom."""
+        delta = event.delta[1]
+        if delta > 0:  # Zoom in
+            factor = 0.9
+        elif delta < 0:  # Zoom out
+            factor = 1 / 0.9
+        for _ in range(int(abs(delta))):
+            self.zoom(factor, event.pos)
+
+    def on_key_press(self, event):
+        """Use + or - to zoom in and out.
+
+        The mouse wheel can be used to zoom, but some people don't have mouse
+        wheels :)
+
+        """
+        if event.text == '+':
+            self.zoom(0.9)
+        elif event.text == '-':
+            self.zoom(1/0.9)
+
+    def zoom(self, factor, mouse_coords=None):
+        """Factors less than zero zoom in, and greater than zero zoom out.
+
+        If mouse_coords is given, the point under the mouse stays stationary
+        while zooming. mouse_coords should come from MouseEvent.pos.
+
+        """
+        if mouse_coords:  # Record the position of the mouse
+            x, y = float(mouse_coords[0]), float(mouse_coords[1])
+            x0, y0 = self.pixel_to_coords(x, y)
+
+        self.scale *= factor
+        self.scale = max(min(self.scale, self.max_scale), self.min_scale)
+        self.program["scale"] = self.scale
+
+        if mouse_coords:  # Translate so the mouse point is stationary
+            x1, y1 = self.pixel_to_coords(x, y)
+            self.translate_center(x1 - x0, y1 - y0)
+
+
+if __name__ == '__main__':
+    canvas = Canvas(size=(800, 800), keys='interactive')
+    canvas.show()
+    app.run()
diff --git a/examples/demo/markers.py b/examples/demo/gloo/markers.py
similarity index 86%
rename from examples/demo/markers.py
rename to examples/demo/gloo/markers.py
index f46630f..92ecede 100644
--- a/examples/demo/markers.py
+++ b/examples/demo/gloo/markers.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team All rights reserved.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 # -----------------------------------------------------------------------------
 """
@@ -10,6 +10,8 @@ available marker function (marker_disc, marker_diamond, ...)
 
 
 vert = """
+#version 120
+
 // Uniforms
 // ------------------------------------
 uniform mat4 u_model;
@@ -40,13 +42,16 @@ void main (void) {
     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_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 = """
+#version 120
+
 // Constants
 // ------------------------------------
 
@@ -66,7 +71,7 @@ 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;
 
@@ -108,8 +113,8 @@ float marker(vec2 P, float size)
 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 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;
 }
@@ -163,7 +168,7 @@ float marker(vec2 P, float size)
 diamond = """
 float marker(vec2 P, float size)
 {
-    float r = abs(P.x -.5)*size + abs(P.y -.5)*size; 
+    float r = abs(P.x -.5)*size + abs(P.y -.5)*size;
     r -= v_size/2;
     return r;
 }
@@ -173,8 +178,8 @@ float marker(vec2 P, float size)
 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 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;
@@ -185,8 +190,8 @@ float marker(vec2 P, float size)
 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 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;
@@ -197,33 +202,31 @@ float marker(vec2 P, float size)
 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 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= """
+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 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 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/gloo/molecular_viewer.py b/examples/demo/gloo/molecular_viewer.py
new file mode 100644
index 0000000..c6a6c7e
--- /dev/null
+++ b/examples/demo/gloo/molecular_viewer.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# 2014, Aurore Deschildre, Gael Goret, Cyrille Rossant, Nicolas P. Rougier.
+# Distributed under the terms of the new BSD License.
+# -----------------------------------------------------------------------------
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.io import load_data_file
+
+vertex = """
+#version 120
+
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform vec3 u_light_position;
+uniform vec3 u_light_spec_position;
+
+attribute vec3  a_position;
+attribute vec3  a_color;
+attribute float a_radius;
+
+varying vec3  v_color;
+varying vec4  v_eye_position;
+varying float v_radius;
+varying vec3  v_light_direction;
+
+void main (void) {
+    v_radius = a_radius;
+    v_color = a_color;
+
+    v_eye_position = u_view * u_model * vec4(a_position,1.0);
+    v_light_direction = normalize(u_light_position);
+    float dist = length(v_eye_position.xyz);
+
+    gl_Position = u_projection * v_eye_position;
+
+    // stackoverflow.com/questions/8608844/...
+    //  ... resizing-point-sprites-based-on-distance-from-the-camera
+    vec4  proj_corner = u_projection * vec4(a_radius, a_radius, v_eye_position.z, v_eye_position.w);  // # noqa
+    gl_PointSize = 512.0 * proj_corner.x / proj_corner.w;
+}
+"""
+
+fragment = """
+#version 120
+
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform vec3 u_light_position;
+uniform vec3 u_light_spec_position;
+
+varying vec3  v_color;
+varying vec4  v_eye_position;
+varying float v_radius;
+varying vec3  v_light_direction;
+void main()
+{
+    // r^2 = (x - x0)^2 + (y - y0)^2 + (z - z0)^2
+    vec2 texcoord = gl_PointCoord* 2.0 - vec2(1.0);
+    float x = texcoord.x;
+    float y = texcoord.y;
+    float d = 1.0 - x*x - y*y;
+    if (d <= 0.0)
+        discard;
+
+    float z = sqrt(d);
+    vec4 pos = v_eye_position;
+    pos.z += v_radius*z;
+    vec3 pos2 = pos.xyz;
+    pos = u_projection * pos;
+    gl_FragDepth = 0.5*(pos.z / pos.w)+0.5;
+    vec3 normal = vec3(x,y,z);
+    float diffuse = clamp(dot(normal, v_light_direction), 0.0, 1.0);
+
+    // Specular lighting.
+    vec3 M = pos2.xyz;
+    vec3 O = v_eye_position.xyz;
+    vec3 L = u_light_spec_position;
+    vec3 K = normalize(normalize(L - M) + normalize(O - M));
+    // WARNING: abs() is necessary, otherwise weird bugs may appear with some
+    // GPU drivers...
+    float specular = clamp(pow(abs(dot(normal, K)), 40.), 0.0, 1.0);
+    vec3 v_light = vec3(1., 1., 1.);
+    gl_FragColor.rgb = (.15*v_color + .55*diffuse * v_color
+                        + .35*specular * v_light);
+}
+"""
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, title='Molecular viewer',
+                            keys='interactive')
+        self.size = 1200, 800
+
+        self.program = gloo.Program(vertex, fragment)
+        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 = 40
+        translate(self.view, 0, 0, -self.translate)
+
+        fname = load_data_file('molecular_viewer/micelle.npz')
+        self.load_molecule(fname)
+        self.load_data()
+
+        self.theta = 0
+        self.phi = 0
+
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+
+    def load_molecule(self, fname):
+        molecule = np.load(fname)['molecule']
+        self._nAtoms = molecule.shape[0]
+
+        # The x,y,z values store in one array
+        self.coords = molecule[:, :3]
+
+        # The array that will store the color and alpha scale for all the atoms
+        self.atomsColours = molecule[:, 3:6]
+
+        # The array that will store the scale for all the atoms.
+        self.atomsScales = molecule[:, 6]
+
+    def load_data(self):
+        n = self._nAtoms
+
+        data = np.zeros(n, [('a_position', np.float32, 3),
+                            ('a_color', np.float32, 3),
+                            ('a_radius', np.float32, 1)])
+
+        data['a_position'] = self.coords
+        data['a_color'] = self.atomsColours
+        data['a_radius'] = self.atomsScales
+
+        self.program.bind(gloo.VertexBuffer(data))
+
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.program['u_light_position'] = 0., 0., 2.
+        self.program['u_light_spec_position'] = -5., 5., -5.
+
+    def on_initialize(self, event):
+        gloo.set_state(depth_test=True, clear_color='black')
+
+    def on_key_press(self, event):
+        if event.text == ' ':
+            if self.timer.running:
+                self.timer.stop()
+            else:
+                self.timer.start()
+        # if event.text == 'A':
+            # self.
+
+    def on_timer(self, event):
+        self.theta += .25
+        self.phi += .25
+        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
+        gloo.set_viewport(0, 0, width, height)
+        self.projection = perspective(25.0, width / float(height), 2.0, 100.0)
+        self.program['u_projection'] = self.projection
+
+    def on_mouse_wheel(self, event):
+        self.translate -= event.delta[1]
+        self.translate = max(-1, 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_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
+
+
+if __name__ == '__main__':
+    mvc = Canvas()
+    mvc.show()
+    app.run()
diff --git a/examples/demo/gloo/ndscatter.py b/examples/demo/gloo/ndscatter.py
new file mode 100644
index 0000000..0e12629
--- /dev/null
+++ b/examples/demo/gloo/ndscatter.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 2000
+
+"""N-dimensional scatter plot with GPU-based projections.
+The projection axes evolve smoothly over time, following a path on the
+Lie group SO(n).
+"""
+
+from vispy import gloo
+from vispy import app
+from vispy.color import ColorArray
+from vispy.io import load_iris
+import numpy as np
+from scipy.linalg import expm, logm
+
+
+class OrthogonalPath(object):
+    """Implement a continuous path on the Lie group SO(n).
+    
+            >>> op = OrthogonalPath(mat1, mat2)
+            >>> mat = op(t)
+    
+    """
+    def __init__(self, mat, origin=None):
+        if origin is None:
+            origin = np.eye(len(mat))
+        self.a, self.b = np.matrix(origin), np.matrix(mat)
+        self._logainvb = logm(self.a.I * self.b)
+        
+    def __call__(self, t):
+        return np.real(self.a * expm(t * self._logainvb))
+
+# Load the Iris dataset and normalize.
+iris = load_iris()
+position = iris['data'].astype(np.float32)
+n, ndim = position.shape
+position -= position.mean()
+position /= np.abs(position).max()
+v_position = position*.75
+
+v_color = ColorArray(['orange', 'magenta', 'darkblue'])
+v_color = v_color.rgb[iris['group'], :].astype(np.float32)
+v_color *= np.random.uniform(.5, 1.5, (n, 3))
+v_color = np.clip(v_color, 0, 1)
+v_size = np.random.uniform(2, 12, (n, 1)).astype(np.float32)
+
+VERT_SHADER = """
+#version 120
+attribute vec4 a_position;
+attribute vec3 a_color;
+attribute float a_size;
+
+uniform vec2 u_pan;
+uniform vec2 u_scale;
+uniform vec4 u_vec1;
+uniform vec4 u_vec2;
+
+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);
+    
+    vec2 position = vec2(dot(a_position, u_vec1),
+                         dot(a_position, u_vec2));
+    
+    vec2 position_tr = u_scale * (position + u_pan);
+    gl_Position = vec4(position_tr, 0.0, 1.0);
+    gl_PointSize = 2.0*(v_radius + v_linewidth + 1.5*v_antialias);
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+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.0*(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, position=(50, 50), keys='interactive')
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+
+        self.program['a_position'] = gloo.VertexBuffer(v_position)
+        self.program['a_color'] = gloo.VertexBuffer(v_color)
+        self.program['a_size'] = gloo.VertexBuffer(v_size)
+        
+        self.program['u_pan'] = (0., 0.)
+        self.program['u_scale'] = (1., 1.)
+        
+        self.program['u_vec1'] = (1., 0., 0., 0.)
+        self.program['u_vec2'] = (0., 1., 0., 0.)
+            
+        # Circulant matrix.
+        circ = np.diagflat(np.ones(ndim-1), 1)
+        circ[-1, 0] = -1 if ndim % 2 == 0 else 1
+        self._op = OrthogonalPath(np.eye(ndim), circ)
+        
+        self._timer = app.Timer('auto', connect=self.on_timer)
+
+    def on_timer(self, event):
+        mat = self._op(event.elapsed)
+        self.program['u_vec1'] = mat[:, 0].squeeze()
+        self.program['u_vec2'] = mat[:, 1].squeeze()
+        self.update()
+        
+    def on_initialize(self, event):
+        gloo.set_state(clear_color=(1, 1, 1, 1), blend=True, 
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+        self._timer.start()
+
+    def on_resize(self, event):
+        self.width, self.height = event.size
+        gloo.set_viewport(0, 0, self.width, self.height)
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/gloo/offscreen.py b/examples/demo/gloo/offscreen.py
new file mode 100644
index 0000000..6d1e104
--- /dev/null
+++ b/examples/demo/gloo/offscreen.py
@@ -0,0 +1,143 @@
+# !/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Demonstrate how to do offscreen rendering.
+Possible use cases:
+
+  * GPGPU without CUDA or OpenCL
+  * creation of scripted animations
+  * remote and Web backends
+
+The method consists of:
+
+  1. Not showing the canvas (show=False).
+  2. Rendering to an FBO.
+  3. Manually triggering a rendering pass with self.update().
+  4. Retrieving the scene with _screenshot().
+  5. Closing the app after the first rendering pass (if that's the intended
+     scenario).
+
+"""
+
+from vispy import gloo
+from vispy import app
+from vispy.util.ptime import time
+from vispy.gloo.util import _screenshot
+import numpy as np
+
+# WARNING: doesn't work with Qt4 (update() does not call on_draw()??)
+app.use_app('glfw')
+
+vertex = """
+attribute vec2 position;
+
+void main()
+{
+    gl_Position = vec4(position, 0, 1.0);
+}
+"""
+
+fragment = """
+uniform vec2 resolution;
+uniform vec2 center;
+uniform float scale;
+uniform int iter;
+
+// Jet color scheme
+vec4 color_scheme(float x) {
+    vec3 a, b;
+    float c;
+    if (x < 0.34) {
+        a = vec3(0, 0, 0.5);
+        b = vec3(0, 0.8, 0.95);
+        c = (x - 0.0) / (0.34 - 0.0);
+    } else if (x < 0.64) {
+        a = vec3(0, 0.8, 0.95);
+        b = vec3(0.85, 1, 0.04);
+        c = (x - 0.34) / (0.64 - 0.34);
+    } else if (x < 0.89) {
+        a = vec3(0.85, 1, 0.04);
+        b = vec3(0.96, 0.7, 0);
+        c = (x - 0.64) / (0.89 - 0.64);
+    } else {
+        a = vec3(0.96, 0.7, 0);
+        b = vec3(0.5, 0, 0);
+        c = (x - 0.89) / (1.0 - 0.89);
+    }
+    return vec4(mix(a, b, c), 1.0);
+}
+
+void main() {
+    vec2 z, c;
+
+    // Recover coordinates from pixel coordinates
+    c.x = (gl_FragCoord.x / resolution.x - 0.5) * scale + center.x;
+    c.y = (gl_FragCoord.y / resolution.y - 0.5) * scale + center.y;
+
+    // Main Mandelbrot computation
+    int i;
+    z = c;
+    for(i = 0; i < iter; i++) {
+        float x = (z.x * z.x - z.y * z.y) + c.x;
+        float y = (z.y * z.x + z.x * z.y) + c.y;
+
+        if((x * x + y * y) > 4.0) break;
+        z.x = x;
+        z.y = y;
+    }
+
+    // Convert iterations to color
+    float color = 1.0 - float(i) / float(iter);
+    gl_FragColor = color_scheme(color);
+
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self, size=None):
+        # We hide the canvas upon creation.
+        app.Canvas.__init__(self, show=False, size=size)
+        self._t0 = time()
+        # Texture where we render the scene.
+        self._rendertex = gloo.Texture2D(shape=self.size, dtype=np.float32)
+        # FBO.
+        self._fbo = gloo.FrameBuffer(self._rendertex,
+                                     gloo.DepthBuffer(self.size))
+        # Regular program that will be rendered to the FBO.
+        self.program = gloo.Program(vertex, fragment)
+        self.program["position"] = [(-1, -1), (-1, 1), (1, 1),
+                                    (-1, -1), (1, 1), (1, -1)]
+        self.program["scale"] = 3
+        self.program["center"] = [-0.5, 0]
+        self.program["iter"] = 300
+        self.program['resolution'] = self.size
+        # We manually draw the hidden canvas.
+        self.update()
+
+    def on_draw(self, event):
+        # Render in the FBO.
+        with self._fbo:
+            gloo.clear('black')
+            gloo.set_viewport(0, 0, *self.size)
+            self.program.draw()
+            # Retrieve the contents of the FBO texture.
+            self.im = _screenshot((0, 0, self.size[0], self.size[1]))
+        self._time = time() - self._t0
+        # Immediately exit the application.
+        app.quit()
+
+if __name__ == '__main__':
+    size = (600, 600)
+    c = Canvas(size=size)
+    app.run()
+    # The rendering is done, we get the rendering output (4D NumPy array)
+    render = c.im
+    print('Finished in %.1fms.' % (c._time*1e3))
+    
+    # Now, we display this image with matplotlib to check.
+    import matplotlib.pyplot as plt
+    plt.figure(figsize=(size[0]/100., size[1]/100.), dpi=100)
+    plt.imshow(render, interpolation='none')
+    plt.show()
diff --git a/examples/demo/gloo/rain.py b/examples/demo/gloo/rain.py
new file mode 100755
index 0000000..a20e6ac
--- /dev/null
+++ b/examples/demo/gloo/rain.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author:   Nicolas P .Rougier
+# Date:     06/03/2014
+# Abstract: Water ripple effect following mouse
+# Keywords: antialias, water, mouse
+# -----------------------------------------------------------------------------
+
+import numpy as np
+
+from vispy import gloo, app
+from vispy.gloo import Program, VertexBuffer
+from vispy.util.transforms import ortho
+
+vertex = """
+#version 120
+
+uniform mat4  u_model;
+uniform mat4  u_view;
+uniform mat4  u_projection;
+uniform float u_linewidth;
+uniform float u_antialias;
+
+attribute vec3  a_position;
+attribute vec4  a_fg_color;
+attribute float a_size;
+
+varying vec4  v_fg_color;
+varying float v_size;
+
+void main (void)
+{
+    v_size = a_size;
+    v_fg_color = a_fg_color;
+    if( a_fg_color.a > 0.0)
+    {
+        gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+        gl_PointSize = v_size + u_linewidth + 2*1.5*u_antialias;
+    }
+    else
+    {
+        gl_Position = u_projection * u_view * u_model * vec4(-1,-1,0,1);
+        gl_PointSize = 0.0;
+    }
+}
+"""
+
+fragment = """
+#version 120
+
+uniform float u_linewidth;
+uniform float u_antialias;
+varying vec4  v_fg_color;
+varying vec4  v_bg_color;
+varying float v_size;
+float disc(vec2 P, float size)
+{
+    return length((P.xy - vec2(0.5,0.5))*size);
+}
+void main()
+{
+    if( v_fg_color.a <= 0.0)
+        discard;
+    float actual_size = v_size + u_linewidth + 2*1.5*u_antialias;
+    float t = u_linewidth/2.0 - u_antialias;
+    float r = disc(gl_PointCoord, actual_size);
+    float d = abs(r - v_size/2.0) - t;
+    if( d < 0.0 )
+    {
+         gl_FragColor = v_fg_color;
+    }
+    else if( abs(d) > 2.5*u_antialias )
+    {
+         discard;
+    }
+    else
+    {
+        d /= u_antialias;
+        gl_FragColor = vec4(v_fg_color.rgb, exp(-d*d)*v_fg_color.a);
+    }
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, title='Rain [Move mouse]',
+                            size=(512, 512), keys='interactive')
+
+    def on_initialize(self, event):
+        # Build data
+        # --------------------------------------
+        n = 500
+        self.data = np.zeros(n, [('a_position', np.float32, 2),
+                                 ('a_fg_color', np.float32, 4),
+                                 ('a_size',     np.float32, 1)])
+        self.index = 0
+        self.program = Program(vertex, fragment)
+        self.vdata = VertexBuffer(self.data)
+        self.program.bind(self.vdata)
+        self.program['u_antialias'] = 1.00
+        self.program['u_linewidth'] = 1.00
+        self.program['u_model'] = np.eye(4, dtype=np.float32)
+        self.program['u_view'] = np.eye(4, dtype=np.float32)
+        gloo.set_clear_color('white')
+        gloo.set_state(blend=True,
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+        self.timer = app.Timer('auto', self.on_timer, start=True)
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+        projection = ortho(0, event.size[0], 0, event.size[1], -1, +1)
+        self.program['u_projection'] = projection
+
+    def on_timer(self, event):
+        self.data['a_fg_color'][..., 3] -= 0.01
+        self.data['a_size'] += 1.0
+        self.vdata.set_data(self.data)
+        self.update()
+
+    def on_mouse_move(self, event):
+        x, y = event.pos
+        h = gloo.get_parameter('viewport')[3]
+        self.data['a_position'][self.index] = x, h - y
+        self.data['a_size'][self.index] = 5
+        self.data['a_fg_color'][self.index] = 0, 0, 0, 1
+        self.index = (self.index + 1) % 500
+
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/demo/gloo/raytracing.py b/examples/demo/gloo/raytracing.py
new file mode 100644
index 0000000..9e3d0d2
--- /dev/null
+++ b/examples/demo/gloo/raytracing.py
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 300
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+"""
+GPU-based ray tracing example.
+
+GLSL port of the following Python example:
+    https://gist.github.com/rossant/6046463
+    https://pbs.twimg.com/media/BPpbJTiCIAEoEPl.png
+
+TODO:
+    * Once uniform structs are supported, refactor the code to encapsulate
+      objects (spheres, planes, lights) in structures.
+    * Customizable engine with an arbitrary number of objects.
+"""
+
+from math import cos
+from vispy import app, gloo
+
+vertex = """
+#version 120
+
+attribute vec2 a_position;
+varying vec2 v_position;
+void main()
+{
+    gl_Position = vec4(a_position, 0.0, 1.0);
+    v_position = a_position;
+}
+"""
+
+fragment = """
+#version 120
+
+const float M_PI = 3.14159265358979323846;
+const float INFINITY = 1000000000.;
+const int PLANE = 1;
+const int SPHERE_0 = 2;
+const int SPHERE_1 = 3;
+
+uniform float u_time;
+uniform float u_aspect_ratio;
+varying vec2 v_position;
+
+uniform vec3 sphere_position_0;
+uniform float sphere_radius_0;
+uniform vec3 sphere_color_0;
+
+uniform vec3 sphere_position_1;
+uniform float sphere_radius_1;
+uniform vec3 sphere_color_1;
+
+uniform vec3 plane_position;
+uniform vec3 plane_normal;
+
+uniform float light_intensity;
+uniform vec2 light_specular;
+uniform vec3 light_position;
+uniform vec3 light_color;
+
+uniform float ambient;
+uniform vec3 O;
+
+float intersect_sphere(vec3 O, vec3 D, vec3 S, float R) {
+    float a = dot(D, D);
+    vec3 OS = O - S;
+    float b = 2. * dot(D, OS);
+    float c = dot(OS, OS) - R * R;
+    float disc = b * b - 4. * a * c;
+    if (disc > 0.) {
+        float distSqrt = sqrt(disc);
+        float q = (-b - distSqrt) / 2.0;
+        if (b >= 0.) {
+            q = (-b + distSqrt) / 2.0;
+        }
+        float t0 = q / a;
+        float t1 = c / q;
+        t0 = min(t0, t1);
+        t1 = max(t0, t1);
+        if (t1 >= 0.) {
+            if (t0 < 0.) {
+                return t1;
+            }
+            else {
+                return t0;
+            }
+        }
+    }
+    return INFINITY;
+}
+
+float intersect_plane(vec3 O, vec3 D, vec3 P, vec3 N) {
+    float denom = dot(D, N);
+    if (abs(denom) < 1e-6) {
+        return INFINITY;
+    }
+    float d = dot(P - O, N) / denom;
+    if (d < 0.) {
+        return INFINITY;
+    }
+    return d;
+}
+
+vec3 run(float x, float y, float t) {
+    vec3 Q = vec3(x, y, 0.);
+    vec3 D = normalize(Q - O);
+    int depth = 0;
+    float t_plane, t0, t1;
+    vec3 rayO = O;
+    vec3 rayD = D;
+    vec3 col = vec3(0.0, 0.0, 0.0);
+    vec3 col_ray;
+    float reflection = 1.;
+    
+    int object_index;
+    vec3 object_color;
+    vec3 object_normal;
+    float object_reflection;
+    vec3 M;
+    vec3 N, toL, toO;
+    
+    while (depth < 5) {
+        
+        /* start trace_ray */
+        
+        t_plane = intersect_plane(rayO, rayD, plane_position, plane_normal);
+        t0 = intersect_sphere(rayO, rayD, sphere_position_0, sphere_radius_0);
+        t1 = intersect_sphere(rayO, rayD, sphere_position_1, sphere_radius_1);
+        
+        if (t_plane < min(t0, t1)) {
+            // Plane.
+            M = rayO + rayD * t_plane;
+            object_normal = plane_normal;
+            // Plane texture.
+            if (mod(int(2*M.x), 2) == mod(int(2*M.z), 2)) {
+                object_color = vec3(1., 1., 1.);
+            }
+            else {
+                object_color = vec3(0., 0., 0.);
+            }
+            object_reflection = .25;
+            object_index = PLANE;
+        }
+        else if (t0 < t1) {
+            // Sphere 0.
+            M = rayO + rayD * t0;
+            object_normal = normalize(M - sphere_position_0);
+            object_color = sphere_color_0;
+            object_reflection = .5;
+            object_index = SPHERE_0;
+        }
+        else if (t1 < t0) {
+            // Sphere 1.
+            M = rayO + rayD * t1;
+            object_normal = normalize(M - sphere_position_1);
+            object_color = sphere_color_1;
+            object_reflection = .5;
+            object_index = SPHERE_1;
+        }
+        else {
+            break;
+        }
+        
+        N = object_normal;
+        toL = normalize(light_position - M);
+        toO = normalize(O - M);
+        
+        // Shadow of the spheres on the plane.
+        if (object_index == PLANE) {
+            t0 = intersect_sphere(M + N * .0001, toL, 
+                                  sphere_position_0, sphere_radius_0);
+            t1 = intersect_sphere(M + N * .0001, toL, 
+                                  sphere_position_1, sphere_radius_1);
+            if (min(t0, t1) < INFINITY) {
+                break;
+            }
+        }
+        
+        col_ray = vec3(ambient, ambient, ambient);
+        col_ray += light_intensity * max(dot(N, toL), 0.) * object_color;
+        col_ray += light_specular.x * light_color * 
+            pow(max(dot(N, normalize(toL + toO)), 0.), light_specular.y);
+        
+        /* end trace_ray */
+        
+        rayO = M + N * .0001;
+        rayD = normalize(rayD - 2. * dot(rayD, N) * N);
+        col += reflection * col_ray;
+        reflection *= object_reflection;
+        
+        depth++;
+    }
+    
+    return clamp(col, 0., 1.);
+}
+
+void main() {
+    vec2 pos = v_position;
+    gl_FragColor = vec4(run(pos.x*u_aspect_ratio, pos.y, u_time), 1.);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, position=(300, 100), 
+                            size=(800, 600), keys='interactive')
+        
+        self.program = gloo.Program(vertex, fragment)
+        self.program['a_position'] = [(-1., -1.), (-1., +1.),
+                                      (+1., -1.), (+1., +1.)]
+
+        self.program['sphere_position_0'] = (.75, .1, 1.)
+        self.program['sphere_radius_0'] = .6
+        self.program['sphere_color_0'] = (0., 0., 1.)
+        
+        self.program['sphere_position_1'] = (-.75, .1, 2.25)
+        self.program['sphere_radius_1'] = .6
+        self.program['sphere_color_1'] = (.5, .223, .5)
+
+        self.program['plane_position'] = (0., -.5, 0.)
+        self.program['plane_normal'] = (0., 1., 0.)
+        
+        self.program['light_intensity'] = 1.
+        self.program['light_specular'] = (1., 50.)
+        self.program['light_position'] = (5., 5., -10.)
+        self.program['light_color'] = (1., 1., 1.)
+        self.program['ambient'] = .05
+        self.program['O'] = (0., 0., -1.)
+                                      
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+    
+    def on_timer(self, event):
+        t = event.elapsed
+        self.program['u_time'] = t
+        self.program['sphere_position_0'] = (+.75, .1, 2.0 + 1.0 * cos(4*t))
+        self.program['sphere_position_1'] = (-.75, .1, 2.0 - 1.0 * cos(4*t))
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        self.program['u_aspect_ratio'] = width/float(height)
+
+    def on_draw(self, event):
+        self.program.draw('triangle_strip')
+
+if __name__ == '__main__':
+    canvas = Canvas()
+    canvas.show()
+    app.run()
diff --git a/examples/demo/gloo/realtime_signals.py b/examples/demo/gloo/realtime_signals.py
new file mode 100644
index 0000000..07c8839
--- /dev/null
+++ b/examples/demo/gloo/realtime_signals.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Multiple real-time digital signals with GLSL-based clipping.
+"""
+
+from vispy import gloo
+from vispy import app
+import numpy as np
+import math
+
+# Number of cols and rows in the table.
+nrows = 16
+ncols = 20
+
+# Number of signals.
+m = nrows*ncols
+
+# Number of samples per signal.
+n = 1000
+
+# Various signal amplitudes.
+amplitudes = .1 + .2 * np.random.rand(m, 1).astype(np.float32)
+
+# Generate the signals as a (m, n) array.
+y = amplitudes * np.random.randn(m, n).astype(np.float32)
+
+# Color of each vertex (TODO: make it more efficient by using a GLSL-based 
+# color map and the index).
+color = np.repeat(np.random.uniform(size=(m, 3), low=.5, high=.9),
+                  n, axis=0).astype(np.float32)
+
+# Signal 2D index of each vertex (row and col) and x-index (sample index
+# within each signal).
+index = np.c_[np.repeat(np.repeat(np.arange(ncols), nrows), n),
+              np.repeat(np.tile(np.arange(nrows), ncols), n),
+              np.tile(np.arange(n), m)].astype(np.float32)
+
+VERT_SHADER = """
+#version 120
+
+// y coordinate of the position.
+attribute float a_position;
+
+// row, col, and time index.
+attribute vec3 a_index;
+varying vec3 v_index;
+
+// 2D scaling factor (zooming).
+uniform vec2 u_scale;
+
+// Size of the table.
+uniform vec2 u_size;
+
+// Number of samples per signal.
+uniform float u_n;
+
+// Color.
+attribute vec3 a_color;
+varying vec4 v_color;
+
+// Varying variables used for clipping in the fragment shader.
+varying vec2 v_position;
+varying vec4 v_ab;
+
+void main() {
+    float nrows = u_size.x;
+    float ncols = u_size.y;
+   
+    // Compute the x coordinate from the time index.
+    float x = -1 + 2*a_index.z / (u_n-1);
+    vec2 position = vec2(x, a_position);
+    
+    // Find the affine transformation for the subplots.
+    vec2 a = vec2(1./ncols, 1./nrows)*.9;
+    vec2 b = vec2(-1 + 2*(a_index.x+.5) / ncols, 
+                  -1 + 2*(a_index.y+.5) / nrows);
+    // Apply the static subplot transformation + scaling.
+    gl_Position = vec4(a*u_scale*position+b, 0.0, 1.0);
+    
+    v_color = vec4(a_color, 1.);
+    v_index = a_index;
+    
+    // For clipping test in the fragment shader.
+    v_position = gl_Position.xy;
+    v_ab = vec4(a, b);
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+
+varying vec4 v_color;
+varying vec3 v_index;
+
+varying vec2 v_position;
+varying vec4 v_ab;
+
+void main() {
+    gl_FragColor = v_color;
+    
+    // Discard the fragments between the signals (emulate glMultiDrawArrays).
+    if ((fract(v_index.x) > 0.) || (fract(v_index.y) > 0.))
+        discard;
+      
+    // Clipping test.
+    vec2 test = abs((v_position.xy-v_ab.zw)/v_ab.xy);
+    if ((test.x > 1) || (test.y > 1))
+        discard;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, title='Use your wheel to zoom!', 
+                            keys='interactive')
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.program['a_position'] = y.ravel()
+        self.program['a_color'] = color
+        self.program['a_index'] = index
+        self.program['u_scale'] = (1., 1.)
+        self.program['u_size'] = (nrows, ncols)
+        self.program['u_n'] = n
+        
+        self._timer = app.Timer('auto', connect=self.on_timer, start=True)
+    
+    def on_initialize(self, event):
+        gloo.set_state(clear_color='black', blend=True, 
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def on_resize(self, event):
+        self.width, self.height = event.size
+        gloo.set_viewport(0, 0, self.width, self.height)
+        
+    def on_mouse_wheel(self, event):
+        dx = np.sign(event.delta[1]) * .05
+        scale_x, scale_y = self.program['u_scale']     
+        scale_x_new, scale_y_new = (scale_x * math.exp(2.5*dx), 
+                                    scale_y * math.exp(0.0*dx))
+        self.program['u_scale'] = (max(1, scale_x_new), max(1, scale_y_new))
+        self.update()
+
+    def on_timer(self, event):
+        """Add some data at the end of each signal (real-time signals)."""
+        k = 10
+        y[:, :-k] = y[:, k:]
+        y[:, -k:] = amplitudes * np.random.randn(m, k)
+        
+        self.program['a_position'].set_data(y.ravel().astype(np.float32))
+        self.update()
+        
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('line_strip')
+        
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/show-markers.py b/examples/demo/gloo/show_markers.py
similarity index 50%
rename from examples/demo/show-markers.py
rename to examples/demo/gloo/show_markers.py
index 7af8fae..3e1460d 100644
--- a/examples/demo/show-markers.py
+++ b/examples/demo/gloo/show_markers.py
@@ -9,59 +9,59 @@ 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
+from vispy.gloo import Program, VertexBuffer
+from vispy import gloo
 
 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 = 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
+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;
+    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
+    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
+    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
+    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)
+        app.Canvas.__init__(self, keys='interactive')
 
         # This size is used for comparison with agg (via matplotlib)
-        self.size = 512,512+2*32
+        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.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),
@@ -73,27 +73,25 @@ class Canvas(app.Canvas):
             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) ]
-                                    
+            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)
+            program.bind(self.vbo)
+            program["u_antialias"] = u_antialias,
+            program["u_size"] = 1
+            program["u_model"] = self.model
+            program["u_view"] = self.view
+            program["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)
+        gloo.set_state(depth_test=False, blend=True, clear_color='white',
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
 
-    def on_key_press(self,event):
+    def on_key_press(self, event):
         if event.text == ' ':
-            self.index = (self.index+1) % (len(self.programs))
+            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
@@ -101,15 +99,15 @@ class Canvas(app.Canvas):
 
     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
+        gloo.set_viewport(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)
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('points')
 
 if __name__ == '__main__':
     canvas = Canvas()
diff --git a/examples/demo/gloo/signals.py b/examples/demo/gloo/signals.py
new file mode 100644
index 0000000..548ea45
--- /dev/null
+++ b/examples/demo/gloo/signals.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Multiple digital signals.
+"""
+
+from vispy import gloo
+from vispy import app
+import numpy as np
+import math
+
+m = 20
+n = 25000
+x = np.tile(np.linspace(-1., 1., n), m)
+y = .1 * np.random.randn(m, n)
+y += np.arange(m).reshape((-1, 1))
+
+data = np.zeros(n*m, dtype=[
+    ('a_position', np.float32, 2),
+    ('a_color', np.float32, 3),
+    ('a_index', np.float32, 1),
+])
+
+data['a_position'] = np.zeros((n*m, 2), dtype=np.float32)
+data['a_position'][:, 0] = x
+data['a_position'][:, 1] = .9*(y.ravel()/y.max()*2-1)
+
+data['a_color'] = np.repeat(np.random.uniform(size=(m, 3), low=.5, high=.9),
+                            n, axis=0)
+
+data['a_index'] = np.repeat(np.arange(m), n)
+
+VERT_SHADER = """
+#version 120
+attribute vec2 a_position;
+attribute float a_index;
+varying float v_index;
+
+attribute vec3 a_color;
+varying vec3 v_color;
+
+uniform vec2 u_pan;
+uniform vec2 u_scale;
+
+void main() {
+    
+    vec2 position_tr = u_scale * (a_position + u_pan);
+    gl_Position = vec4(position_tr, 0.0, 1.0);
+    v_color = a_color;
+    v_index = a_index;
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+varying vec3 v_color;
+varying float v_index;
+void main() {
+    gl_FragColor = vec4(v_color, 1.0);
+    if ((fract(v_index) > .00001) && (fract(v_index) < .99999))
+        gl_FragColor.a = 0.;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.program.bind(gloo.VertexBuffer(data))
+        
+        self.program['u_pan'] = (0., 0.)
+        self.program['u_scale'] = (1., 1.)
+
+    def on_initialize(self, event):
+        gloo.set_state(clear_color=(1, 1, 1, 1), blend=True, 
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def on_resize(self, event):
+        self.width, self.height = event.size
+        gloo.set_viewport(0, 0, self.width, self.height)
+
+    def on_draw(self, event):
+        gloo.clear(color=(0.0, 0.0, 0.0, 1.0))
+        self.program.draw('line_strip')
+
+    def _normalize(self, x_y):
+        x, y = x_y
+        w, h = float(self.width), float(self.height)
+        return x/(w/2.)-1., y/(h/2.)-1.
+            
+    def on_mouse_move(self, event):
+        if event.is_dragging:
+            x0, y0 = self._normalize(event.press_event.pos)
+            x1, y1 = self._normalize(event.last_event.pos)
+            x, y = self._normalize(event.pos)
+            dx, dy = x - x1, -(y - y1)
+            button = event.press_event.button
+            
+            pan_x, pan_y = self.program['u_pan']
+            scale_x, scale_y = self.program['u_scale']
+            
+            if button == 1:
+                self.program['u_pan'] = (pan_x+dx/scale_x, pan_y+dy/scale_y)
+            elif button == 2:
+                scale_x_new, scale_y_new = (scale_x * math.exp(2.5*dx),
+                                            scale_y * math.exp(2.5*dy))
+                self.program['u_scale'] = (scale_x_new, scale_y_new)
+                self.program['u_pan'] = (pan_x - 
+                                         x0 * (1./scale_x - 1./scale_x_new), 
+                                         pan_y + 
+                                         y0 * (1./scale_y - 1./scale_y_new))
+            self.update()
+
+    def on_mouse_wheel(self, event):
+        dx = np.sign(event.delta[1])*.05
+        scale_x, scale_y = self.program['u_scale']     
+        scale_x_new, scale_y_new = (scale_x * math.exp(2.5*dx), 
+                                    scale_y * math.exp(2.5*dx))
+        self.program['u_scale'] = (scale_x_new, scale_y_new)
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/gloo/spacy.py b/examples/demo/gloo/spacy.py
new file mode 100644
index 0000000..7e95a1f
--- /dev/null
+++ b/examples/demo/gloo/spacy.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+# -----------------------------------------------------------------------------
+# 2014, Almar Klein
+# Distributed under the terms of the new BSD License.
+# -----------------------------------------------------------------------------
+
+""" Visualization of traveling through space.
+"""
+
+import time
+
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective
+
+
+vertex = """
+#version 120
+
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform float u_time_offset;
+
+attribute vec3  a_position;
+attribute float a_offset;
+
+varying float v_pointsize;
+
+void main (void) {
+   
+    vec3 pos = a_position;
+    pos.z = pos.z - a_offset - u_time_offset;
+    vec4 v_eye_position = u_view * u_model * vec4(pos, 1.0);
+    gl_Position = u_projection * v_eye_position;
+
+    // stackoverflow.com/questions/8608844/...
+    //  ... resizing-point-sprites-based-on-distance-from-the-camera
+    float radius = 1;
+    vec4 corner = vec4(radius, radius, v_eye_position.z, v_eye_position.w);
+    vec4  proj_corner = u_projection * corner;
+    gl_PointSize = 100.0 * proj_corner.x / proj_corner.w;
+    v_pointsize = gl_PointSize;
+}
+"""
+
+fragment = """
+#version 120
+varying float v_pointsize;
+void main()
+{
+    float x = 2.0*gl_PointCoord.x - 1.0;
+    float y = 2.0*gl_PointCoord.y - 1.0;
+    float a = 0.9 - (x*x + y*y);
+    a = a * min(1.0, v_pointsize/1.5);
+    gl_FragColor = vec4(1.0, 1.0, 1.0, a);
+}
+"""
+
+N = 100000  # Number of stars 
+SIZE = 100
+SPEED = 4.0  # time in seconds to go through one block
+NBLOCKS = 10
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, title='Spacy', keys='interactive')
+        self.size = 800, 600
+        
+        self.program = gloo.Program(vertex, fragment)
+        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.timer = app.Timer('auto', connect=self.update, start=True)
+        
+        # Set uniforms (some are set later)
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        
+        # Set attributes
+        self.program['a_position'] = np.zeros((N, 3), np.float32)
+        self.program['a_offset'] = np.zeros((N,), np.float32)
+        
+        # Init
+        self._timeout = 0
+        self._active_block = 0
+        for i in range(NBLOCKS):
+            self._generate_stars()
+        self._timeout = time.time() + SPEED
+    
+    def on_initialize(self, event):
+        gloo.set_state(clear_color='black', depth_test=False,
+                       blend=True, blend_equation='func_add',
+                       blend_func=('src_alpha', '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_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        far = SIZE*(NBLOCKS-2)
+        self.projection = perspective(25.0, width / float(height), 1.0, far)
+        self.program['u_projection'] = self.projection
+
+    def on_draw(self, event):
+        # Set time offset. Factor runs from 1 to 0
+        # the time offset goes from 0 to size
+        factor = (self._timeout - time.time()) / SPEED
+        self.program['u_time_offset'] = -(1-factor) * SIZE
+        
+        # Draw
+        gloo.clear()
+        self.program.draw('points')
+        
+        # Build new starts if the first block is fully behind us
+        if factor < 0:
+            self._generate_stars()
+    
+    def on_close(self, event):
+        self.timer.stop()
+    
+    def _generate_stars(self):
+        
+        # Get number of stars in each block
+        blocksize = N // NBLOCKS
+        
+        # Update active block
+        self._active_block += 1
+        if self._active_block >= NBLOCKS:
+            self._active_block = 0
+        
+        # Create new position data for the active block
+        pos = np.zeros((blocksize, 3), 'float32') 
+        pos[:, :2] = np.random.normal(0.0, SIZE/2, (blocksize, 2))  # x-y
+        pos[:, 2] = np.random.uniform(0, SIZE, (blocksize,))  # z
+        start_index = self._active_block * blocksize
+        self.program['a_position'].set_subdata(pos, offset=start_index) 
+        
+        #print(start_index)
+        
+        # Set offsets - active block gets offset 0
+        for i in range(NBLOCKS):
+            val = i - self._active_block
+            if val < 0:
+                val += NBLOCKS
+            values = np.ones((blocksize, 1), 'float32') * val * SIZE
+            start_index = i*blocksize
+            self.program['a_offset'].set_subdata(values, offset=start_index) 
+        
+        # Reset timer
+        self._timeout += SPEED
+
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/gloo/terrain.py b/examples/demo/gloo/terrain.py
new file mode 100644
index 0000000..e0fd370
--- /dev/null
+++ b/examples/demo/gloo/terrain.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+
+""" Terrain generation using diamond-square alogrithm
+and Scipy for Delaunay triangulation
+"""
+
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective, translate, xrotate, yrotate
+from vispy.util.transforms import zrotate
+import numpy as np
+from scipy.spatial import Delaunay
+
+# Arrays for storing generated points and triangles
+points = []
+triangles = []
+height = 0.0
+
+
+def generate_terrain(r_min, r_max, c_min, c_max, disp):
+    """Recursively generates terrain using diamond-square algorithm
+    and stores the vertices in points
+    """
+    a = points[r_min][c_min][2]
+    b = points[r_min][c_max][2]
+    c = points[r_max][c_min][2]
+    d = points[r_max][c_max][2]
+
+    r_mid = (r_min + r_max)/2
+    c_mid = (c_min + c_max)/2
+
+    e = (a+b+c+d)/4 + np.random.uniform(0, disp)
+
+    points[r_mid][c_mid][2] = e
+
+    points[r_min][c_mid][2] = (a + b + e)/3 + np.random.uniform(0, disp)
+    points[r_max][c_mid][2] = (c + d + e)/3 + np.random.uniform(0, disp)
+    points[r_mid][c_min][2] = (a + c + e)/3 + np.random.uniform(0, disp)
+    points[r_mid][c_max][2] = (b + d + e)/3 + np.random.uniform(0, disp)
+
+    new_disp = disp * (2 ** (-0.5))
+
+    if (r_mid - r_min > 1 or c_mid - c_min > 1):
+        generate_terrain(r_min, r_mid, c_min, c_mid, new_disp)
+    if (r_max - r_mid > 1 or c_mid - c_min > 1):
+        generate_terrain(r_mid, r_max, c_min, c_mid, new_disp)
+    if (r_mid - r_min > 1 or c_max - c_mid > 1):
+        generate_terrain(r_min, r_mid, c_mid, c_max, new_disp)
+    if (r_max - r_mid > 1 or c_max - c_mid > 1):
+        generate_terrain(r_mid, r_max, c_mid, c_max, new_disp)
+
+
+def generate_points(length=3):
+    """Generates points via recursive function and generate triangles using
+    Scipy Delaunay triangulation
+
+    Parameters
+    ----------
+    length : int
+        (2 ** length + 1 by 2 ** length + 1) number of points is generated
+
+    """
+    print("Points are being generated...")
+    global points, triangles, height
+    size = 2**(length) + 1
+    points = np.indices((size, size, 1)).T[0].transpose((1, 0, 2))
+    points = points.astype(np.float32)
+    generate_terrain(0, size-1, 0, size-1, length)
+    height = length
+    points = np.resize(points, (size*size, 3))
+    points2 = np.delete(points, 2, 1)
+    tri = Delaunay(points2)
+    triangles = points[tri.simplices]
+    triangles = np.vstack(triangles)
+    print("Points successfully generated.")
+
+VERT_SHADER = """
+uniform   float u_height;
+uniform   mat4 u_model;
+uniform   mat4 u_view;
+uniform   mat4 u_projection;
+
+attribute vec3  a_position;
+
+varying vec4 v_color;
+
+void main (void) {
+    gl_Position = u_projection * u_view * u_model * vec4(a_position, 1.0);
+    v_color = vec4(0.0, a_position[2] * a_position[2] / (u_height * u_height
+                   * u_height), 0.1, 1.0);
+}
+"""
+
+FRAG_SHADER = """
+varying vec4 v_color;
+
+void main()
+{
+    gl_FragColor = v_color;
+}
+"""
+
+
+class Canvas(app.Canvas):
+
+    def __init__(self):
+        app.Canvas.__init__(self, keys='interactive')
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        #Sets the view to an appropriate position over the terrain
+        self.default_view = np.array([[0.8, 0.2, -0.48, 0],
+                                     [-0.5, 0.3, -0.78, 0],
+                                     [-0.01, 0.9, -0.3, 0],
+                                     [-4.5, -21.5, -7.4, 1]],
+                                     dtype=np.float32)
+        self.view = self.default_view
+        self.model = np.eye(4, dtype=np.float32)
+        self.projection = np.eye(4, dtype=np.float32)
+
+        self.translate = [0, 0, 0]
+        self.rotate = [0, 0, 0]
+
+        self.program['u_height'] = height
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+
+        self.program['a_position'] = gloo.VertexBuffer(triangles)
+
+    def on_initialize(self, event):
+        gloo.set_state(clear_color='black', depth_test=True)
+
+    def on_key_press(self, event):
+        """Controls -
+        a(A) - move left
+        d(D) - move right
+        w(W) - move up
+        s(S) - move down
+        x/X - rotate about x-axis cw/anti-cw
+        y/Y - rotate about y-axis cw/anti-cw
+        z/Z - rotate about z-axis cw/anti-cw
+        space - reset view
+        p(P) - print current view
+        i(I) - zoom in
+        o(O) - zoom out
+        """
+        self.translate = [0, 0, 0]
+        self.rotate = [0, 0, 0]
+
+        if(event.text == 'p' or event.text == 'P'):
+            print(self.view)
+        elif(event.text == 'd' or event.text == 'D'):
+            self.translate[0] = 0.3
+        elif(event.text == 'a' or event.text == 'A'):
+            self.translate[0] = -0.3
+        elif(event.text == 'w' or event.text == 'W'):
+            self.translate[1] = 0.3
+        elif(event.text == 's' or event.text == 'S'):
+            self.translate[1] = -0.3
+        elif(event.text == 'o' or event.text == 'O'):
+            self.translate[2] = 0.3
+        elif(event.text == 'i' or event.text == 'I'):
+            self.translate[2] = -0.3
+        elif(event.text == 'x'):
+            self.rotate = [1, 0, 0]
+        elif(event.text == 'X'):
+            self.rotate = [-1, 0, 0]
+        elif(event.text == 'y'):
+            self.rotate = [0, 1, 0]
+        elif(event.text == 'Y'):
+            self.rotate = [0, -1, 0]
+        elif(event.text == 'z'):
+            self.rotate = [0, 0, 1]
+        elif(event.text == 'Z'):
+            self.rotate = [0, 0, -1]
+        elif(event.text == ' '):
+            self.view = self.default_view
+
+        translate(self.view, -self.translate[0], -self.translate[1],
+                  -self.translate[2])
+        xrotate(self.view, self.rotate[0])
+        yrotate(self.view, self.rotate[1])
+        zrotate(self.view, self.rotate[2])
+
+        self.program['u_view'] = self.view
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        self.projection = perspective(60.0, width / float(height), 1.0, 100.0)
+        self.program['u_projection'] = self.projection
+
+    def on_draw(self, event):
+        # Clear
+        gloo.clear(color=True, depth=True)
+        # Draw
+        self.program.draw('triangles')
+
+
+generate_points(8)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/gloo/unstructured_2d.py b/examples/demo/gloo/unstructured_2d.py
new file mode 100644
index 0000000..ede88ee
--- /dev/null
+++ b/examples/demo/gloo/unstructured_2d.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author:   Per Rosengren
+# Date:     18/03/2014
+# Abstract: Unstructured2D canvas example
+# Keywords: unstructured delaunay colormap
+# Require: scipy
+# ----------------------------------------------------------------------------
+"""Unstructured2D canvas example.
+
+Takes unstructured 2D locations, with corresponding 1 or 2 dimensional
+scalar "values". Plots the values looked up from colormaps and
+interpolated between the locations.
+"""
+
+import numpy as np
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import ortho
+
+import scipy.spatial
+
+
+class Unstructured2d(app.Canvas):
+
+    def __init__(self,
+                 x=None, y=None, u=None, v=None,
+                 colormap=None, data_lim=None,
+                 dir_x_right=True, dir_y_top=True,
+                 **kwargs):
+        app.Canvas.__init__(self, **kwargs)
+        self.create_shader(colormap)
+        self.create_mesh(x, y, u, v)
+        self.program.bind(self.vbo)
+        if data_lim is not None:
+            self._data_lim = data_lim
+        else:
+            self._data_lim = [[x.min(), x.max()], [y.min(), y.max()]]
+        self._dir_x_right = dir_x_right
+        self._dir_y_top = dir_y_top
+
+    def create_shader(self, colormap):
+        if len(colormap.shape) == 2:
+            args = dict(
+                n_dims="1",
+                tex_t="float",
+                texture2D_arg="vec2(v_texcoord, 0.)")
+        else:
+            args = dict(
+                n_dims="2",
+                tex_t="vec2",
+                texture2D_arg="v_texcoord")
+        vertex = """
+            uniform mat4 model;
+            uniform mat4 view;
+            uniform mat4 projection;
+            uniform sampler2D texture;
+
+            attribute vec2 position;
+            attribute {tex_t} texcoord;
+
+            varying {tex_t} v_texcoord;
+            void main()
+            {{
+                gl_Position = projection * vec4(position, 0.0, 1.0);
+                v_texcoord = texcoord;
+            }}
+        """.format(**args)
+
+        fragment = """
+            uniform sampler2D texture;
+            varying {tex_t} v_texcoord;
+            void main()
+            {{
+                gl_FragColor = texture2D(texture, {texture2D_arg});
+            }}
+        """.format(**args)
+
+        self.program = gloo.Program(vertex, fragment)
+        if len(colormap.shape) == 2:
+            self.program['texture'] = np.ascontiguousarray(
+                colormap[None, :, :])
+        else:
+            self.program['texture'] = colormap
+        self.program['texture'].interpolation = 'linear'
+        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['model'] = self.model
+        self.program['view'] = self.view
+
+    def create_mesh(self, x, y, u, v):
+        tri = scipy.spatial.Delaunay(np.column_stack([x, y]))
+        edges = tri.simplices.astype(np.uint32)
+        uv = []
+        for c in [u, v]:
+            if c is not None:
+                c = c.astype('f4')
+                c = .5 + .5 * c / np.abs(c).max()
+                uv.append(c)
+        data = np.column_stack(
+            [
+                x.astype('f4'),
+                y.astype('f4')
+            ] + uv
+        ).view(dtype=[('position', 'f4', 2),
+                      ('texcoord', 'f4', 2 if v is not None else 1),
+                      ])
+        self.vbo = gloo.VertexBuffer(data)
+        self.index = gloo.IndexBuffer(edges)
+
+    def on_initialize(self, event):
+        gloo.set_state(blend=True, clear_color='white',
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('triangles', self.index)
+
+    def on_resize(self, event):
+        self.resize(*event.size)
+
+    def resize(self, width, height):
+        gloo.set_viewport(0, 0, width, height)
+        data_width = self._data_lim[0][1] - self._data_lim[0][0]
+        data_height = self._data_lim[1][1] - self._data_lim[1][0]
+        data_aspect = data_width / float(data_height)
+        frame_aspect = width / float(height)
+        if frame_aspect >= data_aspect:
+            padding = (frame_aspect * data_height - data_width) / 2.
+            frame_lim = [
+                [self._data_lim[0][0] - padding,
+                 self._data_lim[0][1] + padding],
+                [self._data_lim[1][0],
+                 self._data_lim[1][1]]]
+        else:
+            padding = (data_width / frame_aspect - data_height) / 2.
+            frame_lim = [
+                [self._data_lim[0][0],
+                 self._data_lim[0][1]],
+                [self._data_lim[1][0] - padding,
+                 self._data_lim[1][1] + padding]]
+        args_ortho = frame_lim[0][::(1 if self._dir_x_right else -1)]
+        args_ortho += frame_lim[1][::(1 if self._dir_y_top else -1)]
+        args_ortho += -1000, 1000
+        self.projection = ortho(*args_ortho)
+        self.program['projection'] = self.projection
+
+
+def create_colormap2d_hsv(size=512):
+    import matplotlib.colors
+    import math
+    u, v = np.meshgrid(np.linspace(-1, 1, size), np.linspace(-1, 1, size))
+    hsv = np.ones((size, size, 3), dtype=np.float32)
+    hsv[:, :, 0] = (np.arctan2(u, v) / (2 * math.pi) + .5)
+    hsv[:, :, 1] = np.minimum(1., np.sqrt(u ** 2 + v ** 2))
+    rgb = matplotlib.colors.hsv_to_rgb(hsv)
+    return rgb
+
+
+def create_colormap2d_4dirs(size=512):
+    rgb = np.ones((size, size, 3), dtype=np.float32)
+    hs = size / 2
+    u, v = np.meshgrid(np.linspace(1, 0, hs), np.linspace(1, 0, hs))
+    rgb[:hs, :hs, 0] = 1.
+    rgb[:hs, :hs, 1] = 1. - v + u / 2.
+    rgb[:hs, :hs, 2] = 1. - np.maximum(u, v)
+    u = u[:, ::-1]
+    rgb[:hs, hs:, 0] = 1. - u + v
+    rgb[:hs, hs:, 1] = 1. - np.maximum(u, v)
+    rgb[:hs, hs:, 2] = 1. - v + u
+    v = v[::-1, :]
+    rgb[hs:, hs:, 0] = 1. - np.maximum(u, v)
+    rgb[hs:, hs:, 1] = 1. - u + v
+    rgb[hs:, hs:, 2] = 1. - v + u
+    u = u[:, ::-1]
+    rgb[hs:, :hs, 0] = 1. - v + u / 2.
+    rgb[hs:, :hs, 1] = 1.
+    rgb[hs:, :hs, 2] = 1. - np.maximum(u, v)
+    rgb = np.minimum(1., rgb)
+    return rgb
+
+
+def create_colormap1d_hot(size=512):
+    rgb = np.ones((size, 3), dtype=np.float32)
+    hs = size / 2
+    u = np.linspace(1, 0, hs)
+    rgb[:hs, 0] = 1 - u
+    rgb[:hs, 1] = 1 - u
+    u = u[::-1]
+    rgb[hs:, 1] = 1 - u
+    rgb[hs:, 2] = 1 - u
+    return rgb
+
+if __name__ == '__main__':
+    loc = np.random.random_sample(size=(100, 2))
+    np.random.shuffle(loc)
+    vec = np.empty_like(loc)
+    vec[:, 0] = np.cos(loc[:, 0] * 10)
+    vec[:, 1] = np.cos(loc[:, 1] * 13)
+    width = 500
+    height = 500
+    c1 = Unstructured2d(title="Unstructured 2D - 2D colormap",
+                        size=(width, height), position=(0, 40),
+                        x=loc[:, 0], y=loc[:, 1], u=vec[:, 0], v=vec[:, 1],
+                        colormap=create_colormap2d_4dirs(size=128),
+                        keys='interactive')
+    c2 = Unstructured2d(title="Unstructured 2D - 1D colormap",
+                        size=(width, height), position=(width + 20, 40),
+                        x=loc[:, 0], y=loc[:, 1], u=vec[:, 0],
+                        colormap=create_colormap1d_hot(size=128),
+                        keys='interactive')
+    c1.show()
+    c2.show()
+    app.run()
diff --git a/examples/demo/gloo/voronoi.py b/examples/demo/gloo/voronoi.py
new file mode 100644
index 0000000..1ba0415
--- /dev/null
+++ b/examples/demo/gloo/voronoi.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+# vispy: gallery 30
+"""Computing a Voronoi diagram on the GPU. Shows how to use uniform arrays.
+
+Original version by Xavier Olive (xoolive).
+
+"""
+
+import numpy as np
+
+from vispy import app
+from vispy import gloo
+
+
+# Voronoi shaders.
+VS_voronoi = """
+attribute vec2 a_position;
+
+void main() {
+    gl_Position = vec4(a_position, 0., 1.);
+}
+"""
+
+FS_voronoi = """
+uniform vec2 u_seeds[32];
+uniform vec3 u_colors[32];
+uniform vec2 u_screen;
+
+void main() {
+    float dist = distance(u_screen * u_seeds[0], gl_FragCoord.xy);
+    vec3 color = u_colors[0];
+    for (int i = 1; i < 32; i++) {
+        float current = distance(u_screen * u_seeds[i], gl_FragCoord.xy);
+        if (current < dist) {
+            color = u_colors[i];
+            dist = current;
+        }
+    }
+    gl_FragColor = vec4(color, 1.0);
+}
+"""
+
+
+# Seed point shaders.
+VS_seeds = """
+attribute vec2 a_position;
+
+void main() {
+    gl_Position = vec4(2. * a_position - 1., 0., 1.);
+    gl_PointSize = 10.;
+}
+"""
+
+FS_seeds = """
+varying vec3 v_color;
+void main() {
+    gl_FragColor = vec4(1., 1., 1., 1.);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(600, 600), title='Voronoi diagram',
+                            keys='interactive')
+        
+        self.seeds = np.random.uniform(0, 1,
+                                       size=(32, 2)).astype(np.float32)
+        self.colors = np.random.uniform(0.3, 0.8, 
+                                        size=(32, 3)).astype(np.float32)
+        
+        # Set Voronoi program.
+        self.program_v = gloo.Program(VS_voronoi, FS_voronoi)
+        self.program_v['a_position'] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]
+        # HACK: work-around a bug related to uniform arrays until 
+        # issue #345 is solved.
+        for i in range(32):
+            self.program_v['u_seeds[%d]' % i] = self.seeds[i, :]
+            self.program_v['u_colors[%d]' % i] = self.colors[i, :]
+            
+        # Set seed points program.
+        self.program_s = gloo.Program(VS_seeds, FS_seeds)
+        self.program_s['a_position'] = self.seeds
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.program_v.draw('triangle_strip')
+        self.program_s.draw('points')
+
+    def on_resize(self, event):
+        self.width, self.height = event.size
+        gloo.set_viewport(0, 0, self.width, self.height)
+        self.program_v['u_screen'] = (self.width, self.height)
+        
+    def on_mouse_move(self, event):
+        x, y = event.pos
+        x, y = x/float(self.width), 1-y/float(self.height)
+        self.program_v['u_seeds[0]'] = x, y
+        # TODO: just update the first line in the VBO instead of uploading the
+        # whole array of seed points.
+        self.seeds[0, :] = x, y
+        self.program_s['a_position'].set_data(self.seeds)
+        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
deleted file mode 100644
index 1efd888..0000000
--- a/examples/howto/client-buffers.py
+++ /dev/null
@@ -1,187 +0,0 @@
-# #!/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/rotate-cube.py b/examples/howto/rotate-cube.py
deleted file mode 100644
index 92c51cd..0000000
--- a/examples/howto/rotate-cube.py
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/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
deleted file mode 100644
index d3ef163..0000000
--- a/examples/howto/split-screen.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# #!/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
deleted file mode 100644
index fef1f06..0000000
--- a/examples/howto/start.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# #!/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/ipynb/display_points.ipynb b/examples/ipynb/display_points.ipynb
new file mode 100644
index 0000000..7b78309
--- /dev/null
+++ b/examples/ipynb/display_points.ipynb
@@ -0,0 +1,152 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:059d3c040693141e0b6069792e415d648bb28645da2b4afbcd3b02dfc9a12473"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from vispy import gloo\n",
+      "from vispy import app, use\n",
+      "import numpy as np"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_static')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Create vetices\n",
+      "n = 10000\n",
+      "v_position = 0.25 * np.random.randn(n, 2).astype(np.float32)\n",
+      "v_color = np.random.uniform(0, 1, (n, 3)).astype(np.float32)\n",
+      "v_size = np.random.uniform(2, 12, (n, 1)).astype(np.float32)\n",
+      "\n",
+      "VERT_SHADER = \"\"\"\n",
+      "attribute vec3  a_position;\n",
+      "attribute vec3  a_color;\n",
+      "attribute float a_size;\n",
+      "\n",
+      "varying vec4 v_fg_color;\n",
+      "varying vec4 v_bg_color;\n",
+      "varying float v_radius;\n",
+      "varying float v_linewidth;\n",
+      "varying float v_antialias;\n",
+      "\n",
+      "void main (void) {\n",
+      "    v_radius = a_size;\n",
+      "    v_linewidth = 1.0;\n",
+      "    v_antialias = 1.0;\n",
+      "    v_fg_color  = vec4(0.0,0.0,0.0,0.5);\n",
+      "    v_bg_color  = vec4(a_color,    1.0);\n",
+      "\n",
+      "    gl_Position = vec4(a_position, 1.0);\n",
+      "    gl_PointSize = 2.0*(v_radius + v_linewidth + 1.5*v_antialias);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "FRAG_SHADER = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "varying vec4 v_fg_color;\n",
+      "varying vec4 v_bg_color;\n",
+      "varying float v_radius;\n",
+      "varying float v_linewidth;\n",
+      "varying float v_antialias;\n",
+      "void main()\n",
+      "{\n",
+      "    float size = 2.0*(v_radius + v_linewidth + 1.5*v_antialias);\n",
+      "    float t = v_linewidth/2.0-v_antialias;\n",
+      "    float r = length((gl_PointCoord.xy - vec2(0.5,0.5))*size);\n",
+      "    float d = abs(r - v_radius) - t;\n",
+      "    if( d < 0.0 )\n",
+      "        gl_FragColor = v_fg_color;\n",
+      "    else\n",
+      "    {\n",
+      "        float alpha = d/v_antialias;\n",
+      "        alpha = exp(-alpha*alpha);\n",
+      "        if (r > v_radius)\n",
+      "            gl_FragColor = vec4(v_fg_color.rgb, alpha*v_fg_color.a);\n",
+      "        else\n",
+      "            gl_FragColor = mix(v_bg_color, v_fg_color, alpha);\n",
+      "    }\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Points(app.Canvas):\n",
+      "\n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "    \n",
+      "    def on_initialize(self, event):\n",
+      "        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)\n",
+      "        # Set uniform and attribute\n",
+      "        self.program['a_color'] = gloo.VertexBuffer(v_color)\n",
+      "        self.program['a_position'] = gloo.VertexBuffer(v_position)\n",
+      "        self.program['a_size'] = gloo.VertexBuffer(v_size)\n",
+      "        gloo.set_state(clear_color='white', blend=True,\n",
+      "                       blend_func=('src_alpha', 'one_minus_src_alpha'))\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        gloo.set_viewport(0, 0, *event.size)\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        gloo.clear(color=True, depth=True)\n",
+      "        self.program.draw('points')\n",
+      "        self.update()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p = Points()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "p.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/display_shape.ipynb b/examples/ipynb/display_shape.ipynb
new file mode 100644
index 0000000..c8bbab4
--- /dev/null
+++ b/examples/ipynb/display_shape.ipynb
@@ -0,0 +1,116 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:b48aa6253c1acca2536eafa8ee7779a21d80867ee71627c50a0dab733b2aa872"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from vispy import gloo\n",
+      "from vispy import app, use\n",
+      "import numpy as np"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_static')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Create vertices\n",
+      "vPosition = np.array([[-0.8, -0.8, 0.0], [+0.7, -0.7, 0.0],\n",
+      "                      [-0.7, +0.7, 0.0], [+0.8, +0.8, 0.0, ]], np.float32)\n",
+      "\n",
+      "\n",
+      "VERT_SHADER = \"\"\" // simple vertex shader\n",
+      "attribute vec3 a_position;\n",
+      "void main (void) {\n",
+      "    gl_Position = vec4(a_position, 1.0);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "FRAG_SHADER = \"\"\" // simple fragment shader\n",
+      "uniform vec4 u_color;\n",
+      "void main()\n",
+      "{\n",
+      "    gl_FragColor = u_color;\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Canvas(app.Canvas):\n",
+      "\n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "\n",
+      "        # Create program\n",
+      "        self._program = gloo.Program(VERT_SHADER, FRAG_SHADER)\n",
+      "\n",
+      "        # Set uniform and attribute\n",
+      "        self._program['u_color'] = 0.2, 1.0, 0.4, 1\n",
+      "        self._program['a_position'] = gloo.VertexBuffer(vPosition)\n",
+      "\n",
+      "    def on_initialize(self, event):\n",
+      "        gloo.set_clear_color('white')\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        width, height = event.size\n",
+      "        gloo.set_viewport(0, 0, width, height)\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        gloo.clear()\n",
+      "        self._program.draw('triangle_strip')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "c = Canvas(size=(500,500))"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "c.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/donut.ipynb b/examples/ipynb/donut.ipynb
new file mode 100644
index 0000000..0fa980c
--- /dev/null
+++ b/examples/ipynb/donut.ipynb
@@ -0,0 +1,255 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:21071064517113790dd14e6e5d596ea2a51c648e6062c756b7e48e8cb533a731"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import numpy as np\n",
+      "from vispy import gloo\n",
+      "from vispy import app, use\n",
+      "from vispy.util.transforms import perspective, translate, rotate"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Create vertices\n",
+      "n, p = 50, 40\n",
+      "data = np.zeros(p * n, [('a_position', np.float32, 2),\n",
+      "                        ('a_bg_color', np.float32, 4),\n",
+      "                        ('a_fg_color', np.float32, 4),\n",
+      "                        ('a_size',     np.float32, 1)])\n",
+      "data['a_position'][:, 0] = np.resize(np.linspace(0, 2 * np.pi, n), p * n)\n",
+      "data['a_position'][:, 1] = np.repeat(np.linspace(0, 2 * np.pi, p), n)\n",
+      "data['a_bg_color'] = np.random.uniform(0.75, 1.00, (n * p, 4))\n",
+      "data['a_bg_color'][:, 3] = 1\n",
+      "data['a_fg_color'] = 0, 0, 0, 1\n",
+      "data['a_size'] = np.random.uniform(8, 8, n * p)\n",
+      "u_linewidth = 1.0\n",
+      "u_antialias = 1.0\n",
+      "u_size = 1\n",
+      "\n",
+      "\n",
+      "vert = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "uniform mat4 u_model;\n",
+      "uniform mat4 u_view;\n",
+      "uniform mat4 u_projection;\n",
+      "uniform float u_linewidth;\n",
+      "uniform float u_antialias;\n",
+      "uniform float u_size;\n",
+      "uniform float u_clock;\n",
+      "\n",
+      "attribute vec2  a_position;\n",
+      "attribute vec4  a_fg_color;\n",
+      "attribute vec4  a_bg_color;\n",
+      "attribute float a_size;\n",
+      "\n",
+      "varying vec4 v_fg_color;\n",
+      "varying vec4 v_bg_color;\n",
+      "varying float v_size;\n",
+      "varying float v_linewidth;\n",
+      "varying float v_antialias;\n",
+      "\n",
+      "void main (void) {\n",
+      "    v_size = a_size * u_size;\n",
+      "    v_linewidth = u_linewidth;\n",
+      "    v_antialias = u_antialias;\n",
+      "    v_fg_color  = a_fg_color;\n",
+      "    v_bg_color  = a_bg_color;\n",
+      "\n",
+      "    float x0 = 0.5;\n",
+      "    float z0 = 0.0;\n",
+      "\n",
+      "    float theta = a_position.x + u_clock;\n",
+      "    float x1 = x0*cos(theta) + z0*sin(theta) - 1.0;\n",
+      "    float y1 = 0.0;\n",
+      "    float z1 = z0*cos(theta) - x0*sin(theta);\n",
+      "\n",
+      "    float phi = a_position.y;\n",
+      "    float x2 = x1*cos(phi) + y1*sin(phi);\n",
+      "    float y2 = y1*cos(phi) - x1*sin(phi);\n",
+      "    float z2 = z1;\n",
+      "\n",
+      "    gl_Position = u_projection * u_view * u_model * vec4(x2,y2,z2,1);\n",
+      "    gl_PointSize = v_size + 2*(v_linewidth + 1.5*v_antialias);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "frag = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "varying vec4 v_fg_color;\n",
+      "varying vec4 v_bg_color;\n",
+      "varying float v_size;\n",
+      "varying float v_linewidth;\n",
+      "varying float v_antialias;\n",
+      "void main()\n",
+      "{\n",
+      "    float size = v_size +2*(v_linewidth + 1.5*v_antialias);\n",
+      "    float t = v_linewidth/2.0-v_antialias;\n",
+      "    float r = length((gl_PointCoord.xy - vec2(0.5,0.5))*size) - v_size/2;\n",
+      "    float d = abs(r) - t;\n",
+      "    if( r > (v_linewidth/2.0+v_antialias))\n",
+      "    {\n",
+      "        discard;\n",
+      "    }\n",
+      "    else if( d < 0.0 )\n",
+      "    {\n",
+      "       gl_FragColor = v_fg_color;\n",
+      "    }\n",
+      "    else\n",
+      "    {\n",
+      "        float alpha = d/v_antialias;\n",
+      "        alpha = exp(-alpha*alpha);\n",
+      "        if (r > 0)\n",
+      "            gl_FragColor = vec4(v_fg_color.rgb, alpha*v_fg_color.a);\n",
+      "        else\n",
+      "            gl_FragColor = mix(v_bg_color, v_fg_color, alpha);\n",
+      "    }\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Donut(app.Canvas):\n",
+      "\n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "\n",
+      "        self.program = gloo.Program(vert, frag)\n",
+      "        self.view = np.eye(4, dtype=np.float32)\n",
+      "        self.model = np.eye(4, dtype=np.float32)\n",
+      "        self.projection = np.eye(4, dtype=np.float32)\n",
+      "        self.translate = 5\n",
+      "        translate(self.view, 0, 0, -self.translate)\n",
+      "\n",
+      "        self.program.bind(gloo.VertexBuffer(data))\n",
+      "        self.program['u_linewidth'] = u_linewidth\n",
+      "        self.program['u_antialias'] = u_antialias\n",
+      "        self.program['u_model'] = self.model\n",
+      "        self.program['u_view'] = self.view\n",
+      "        self.program['u_size'] = 5 / self.translate\n",
+      "\n",
+      "        self.theta = 0\n",
+      "        self.phi = 0\n",
+      "        self.clock = 0\n",
+      "        self.stop_rotation = False\n",
+      "        \n",
+      "        self._timer = app.Timer(1.0 / 60, connect=self.on_timer, start=True)\n",
+      "        \n",
+      "    def on_initialize(self, event):\n",
+      "        gloo.set_state('translucent', clear_color=(1, 1, 1, 1))\n",
+      "\n",
+      "    def on_key_press(self, event):\n",
+      "        self.stop_rotation = not self.stop_rotation\n",
+      "\n",
+      "    def on_timer(self, event):\n",
+      "        if not self.stop_rotation:\n",
+      "            self.theta += .5\n",
+      "            self.phi += .5\n",
+      "            self.model = np.eye(4, dtype=np.float32)\n",
+      "            rotate(self.model, self.theta, 0, 0, 1)\n",
+      "            rotate(self.model, self.phi, 0, 1, 0)\n",
+      "            self.program['u_model'] = self.model\n",
+      "        self.clock += np.pi / 1000\n",
+      "        self.program['u_clock'] = self.clock\n",
+      "        self.update()\n",
+      "        \n",
+      "    def on_resize(self, event):\n",
+      "        width, height = event.size\n",
+      "        gloo.set_viewport(0, 0, width, height)\n",
+      "        self.projection = perspective(45.0, width / float(height), 1.0, 1000.0)\n",
+      "        self.program['u_projection'] = self.projection\n",
+      "\n",
+      "    def on_mouse_wheel(self, event):\n",
+      "        self.translate += event.delta[1]\n",
+      "        self.translate = max(2, self.translate)\n",
+      "        self.view = np.eye(4, dtype=np.float32)\n",
+      "        translate(self.view, 0, 0, -self.translate)\n",
+      "\n",
+      "        self.program['u_view'] = self.view\n",
+      "        self.program['u_size'] = 5 / self.translate\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        gloo.clear()\n",
+      "        self.program.draw('points')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "d = Donut(size=(300,300))"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "d.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "d.size = (800,800) # Resize test"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "d.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/fireworks.ipynb b/examples/ipynb/fireworks.ipynb
new file mode 100644
index 0000000..2071238
--- /dev/null
+++ b/examples/ipynb/fireworks.ipynb
@@ -0,0 +1,195 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:f90783d43a54d451c9c62e63c1b7b8915313de217ac60d5d7db7dafedb3109c8"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import time\n",
+      "import numpy as np\n",
+      "from vispy import gloo\n",
+      "from vispy import app, use"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Create a texture\n",
+      "radius = 32\n",
+      "im1 = np.random.normal(\n",
+      "    0.8, 0.3, (radius * 2 + 1, radius * 2 + 1)).astype(np.float32)\n",
+      "\n",
+      "# Mask it with a disk\n",
+      "L = np.linspace(-radius, radius, 2 * radius + 1)\n",
+      "(X, Y) = np.meshgrid(L, L)\n",
+      "im1 *= np.array((X ** 2 + Y ** 2) <= radius * radius, dtype='float32')\n",
+      "\n",
+      "# Set number of particles, you should be able to scale this to 100000\n",
+      "N = 10000\n",
+      "\n",
+      "# Create vertex data container\n",
+      "data = np.zeros(N, [('a_lifetime', np.float32, 1),\n",
+      "                    ('a_startPosition', np.float32, 3),\n",
+      "                    ('a_endPosition', np.float32, 3)])\n",
+      "\n",
+      "\n",
+      "VERT_SHADER = \"\"\"\n",
+      "#version 120\n",
+      "uniform float u_time;\n",
+      "uniform vec3 u_centerPosition;\n",
+      "attribute float a_lifetime;\n",
+      "attribute vec3 a_startPosition;\n",
+      "attribute vec3 a_endPosition;\n",
+      "varying float v_lifetime;\n",
+      "\n",
+      "void main () {\n",
+      "    if (u_time <= a_lifetime)\n",
+      "    {\n",
+      "        gl_Position.xyz = a_startPosition + (u_time * a_endPosition);\n",
+      "        gl_Position.xyz += u_centerPosition;\n",
+      "        gl_Position.y -= 1.0 * u_time * u_time;\n",
+      "        gl_Position.w = 1.0;\n",
+      "    }\n",
+      "    else\n",
+      "        gl_Position = vec4(-1000, -1000, 0, 0);\n",
+      "\n",
+      "    v_lifetime = 1.0 - (u_time / a_lifetime);\n",
+      "    v_lifetime = clamp(v_lifetime, 0.0, 1.0);\n",
+      "    gl_PointSize = (v_lifetime * v_lifetime) * 40.0;\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "FRAG_SHADER = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "uniform sampler2D texture1;\n",
+      "uniform vec4 u_color;\n",
+      "varying float v_lifetime;\n",
+      "uniform sampler2D s_texture;\n",
+      "\n",
+      "void main()\n",
+      "{\n",
+      "    vec4 texColor;\n",
+      "    texColor = texture2D(s_texture, gl_PointCoord);\n",
+      "    gl_FragColor = vec4(u_color) * texColor;\n",
+      "    gl_FragColor.a *= v_lifetime;\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Firework(app.Canvas):\n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "\n",
+      "        # Create program\n",
+      "        self._program = gloo.Program(VERT_SHADER, FRAG_SHADER)\n",
+      "        self._program.bind(gloo.VertexBuffer(data))\n",
+      "        self._program['s_texture'] = gloo.Texture2D(im1)\n",
+      "        self.explosion((0, 0))\n",
+      "\n",
+      "    def on_initialize(self, event):\n",
+      "        # Enable blending\n",
+      "        gloo.set_state(blend=True, clear_color=(0, 0, 0, 1),\n",
+      "                       blend_func=('src_alpha', 'one'))\n",
+      "    \n",
+      "    def on_mouse_press(self, event):\n",
+      "        self.explosion(event.pos)\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        # Clear\n",
+      "        gloo.clear()\n",
+      "        # Draw\n",
+      "        self._program['u_time'] = time.time() - self._starttime\n",
+      "        self._program.draw('points')\n",
+      "        self.update()\n",
+      "\n",
+      "    def explosion(self, pos):\n",
+      "        # New centerpos\n",
+      "        # centerpos = np.random.uniform(-0.5, 0.5, (3,))\n",
+      "        centerpos = np.ndarray(3)\n",
+      "        centerpos[0] = float(pos[0])/250.0 - 1.0\n",
+      "        centerpos[1] = 1.0 - float(pos[1])/250.0\n",
+      "        centerpos[2] = 0\n",
+      "        self._program['u_centerPosition'] = centerpos\n",
+      "\n",
+      "        # New color, scale alpha with N\n",
+      "        alpha = 1.0 / N ** 0.08\n",
+      "        color = np.random.uniform(0.1, 0.9, (3,))\n",
+      "\n",
+      "        self._program['u_color'] = tuple(color) + (alpha,)\n",
+      "\n",
+      "        # Create new vertex data\n",
+      "        data['a_lifetime'] = np.random.normal(2.0, 0.5, (N,))\n",
+      "        data['a_startPosition'] = np.random.normal(0.0, 0.2, (N, 3))\n",
+      "        data['a_endPosition'] = np.random.normal(0.0, 1.2, (N, 3))\n",
+      "        \n",
+      "        self._starttime = time.time()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "f = Firework(size=(500, 500))"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Click for fireworks!\n",
+      "f.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "f.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/galaxy.ipynb b/examples/ipynb/galaxy.ipynb
new file mode 100644
index 0000000..a1e1431
--- /dev/null
+++ b/examples/ipynb/galaxy.ipynb
@@ -0,0 +1,238 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:7a19dca3e35b17582c088f2d8e18cd4ae2d5b985b3c7c45f9f81fbd27a1532e2"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import numpy as np\n",
+      "\n",
+      "from vispy import gloo\n",
+      "from vispy import app, use\n",
+      "from vispy.util.transforms import perspective, translate, rotate"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "app.use_app('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def make_arm(n, angle):\n",
+      "    R = np.linspace(10, 450 + 50 * np.random.uniform(.5, 1.), n)\n",
+      "    R += 40 * np.random.normal(0, 2., n) * np.linspace(1, .1, n)\n",
+      "    T = angle + np.linspace(0, 2.5 * np.pi, n) + \\\n",
+      "        np.pi / 6 * np.random.normal(0, .5, n)\n",
+      "    S = 8 + 2 * np.abs(np.random.normal(0, 1, n))\n",
+      "    S *= np.linspace(1, .85, n)\n",
+      "    P = np.zeros((n, 3), dtype=np.float32)\n",
+      "    X, Y, Z = P[:, 0], P[:, 1], P[:, 2]\n",
+      "    X[...] = R * np.cos(T)\n",
+      "    Y[...] = R * np.sin(T) * 1.1\n",
+      "    D = np.sqrt(X * X + Y * Y)\n",
+      "    Z[...] = 8 * np.random.normal(0, 2 - D / 512., n)\n",
+      "    X += (D * np.random.uniform(0, 1, n) > 250) * \\\n",
+      "        (.05 * D * np.random.uniform(-1, 1, n))\n",
+      "    Y += (D * np.random.uniform(0, 1, n) > 250) * \\\n",
+      "        (.05 * D * np.random.uniform(-1, 1, n))\n",
+      "    Z += (D * np.random.uniform(0, 1, n) > 250) * \\\n",
+      "        (.05 * D * np.random.uniform(-1, 1, n))\n",
+      "    D = (D - D.min()) / (D.max() - D.min())\n",
+      "\n",
+      "    return P / 256, S / 2, D\n",
+      "p = 50000\n",
+      "n = 3 * p\n",
+      "\n",
+      "data = np.zeros(n, [('a_position', np.float32, 3),\n",
+      "                    ('a_size', np.float32, 1),\n",
+      "                    ('a_dist', np.float32, 1)])\n",
+      "for i in range(3):\n",
+      "    P, S, D = make_arm(p, i * 2 * np.pi / 3)\n",
+      "    data['a_dist'][(i + 0) * p:(i + 1) * p] = D\n",
+      "    data['a_position'][(i + 0) * p:(i + 1) * p] = P\n",
+      "    data['a_size'][(i + 0) * p:(i + 1) * p] = S\n",
+      "\n",
+      "\n",
+      "# Very simple colormap\n",
+      "cmap = np.array([[255, 124, 0], [255, 163, 76],\n",
+      "                 [255, 192, 130], [255, 214, 173],\n",
+      "                 [255, 232, 212], [246, 238, 237],\n",
+      "                 [237, 240, 253], [217, 228, 255],\n",
+      "                 [202, 219, 255], [191, 212, 255],\n",
+      "                 [182, 206, 255], [174, 202, 255],\n",
+      "                 [168, 198, 255], [162, 195, 255],\n",
+      "                 [158, 192, 255], [155, 189, 255],\n",
+      "                 [151, 187, 255], [148, 185, 255],\n",
+      "                 [145, 183, 255], [143, 182, 255],\n",
+      "                 [141, 181, 255], [140, 179, 255],\n",
+      "                 [139, 179, 255],\n",
+      "                 [137, 177, 255]], dtype=np.uint8).reshape(1, 24, 3)\n",
+      "\n",
+      "\n",
+      "VERT_SHADER = \"\"\"\n",
+      "#version 120\n",
+      "// Uniforms\n",
+      "// ------------------------------------\n",
+      "uniform mat4  u_model;\n",
+      "uniform mat4  u_view;\n",
+      "uniform mat4  u_projection;\n",
+      "uniform float u_size;\n",
+      "\n",
+      "\n",
+      "// Attributes\n",
+      "// ------------------------------------\n",
+      "attribute vec3  a_position;\n",
+      "attribute float a_size;\n",
+      "attribute float a_dist;\n",
+      "\n",
+      "// Varyings\n",
+      "// ------------------------------------\n",
+      "varying float v_size;\n",
+      "varying float v_dist;\n",
+      "\n",
+      "void main (void) {\n",
+      "    v_size  = a_size*u_size*.75;\n",
+      "    v_dist  = a_dist;\n",
+      "    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);\n",
+      "    gl_PointSize = v_size;\n",
+      "}\n",
+      "\"\"\"\n",
+      "FRAG_SHADER = \"\"\"\n",
+      "#version 120\n",
+      "// Uniforms\n",
+      "// ------------------------------------\n",
+      "uniform sampler2D u_colormap;\n",
+      "\n",
+      "// Varyings\n",
+      "// ------------------------------------\n",
+      "varying float v_size;\n",
+      "varying float v_dist;\n",
+      "\n",
+      "// Main\n",
+      "// ------------------------------------\n",
+      "void main()\n",
+      "{\n",
+      "    float a = 2*(length(gl_PointCoord.xy - vec2(0.5,0.5)) / sqrt(2.0));\n",
+      "    vec3 color = texture2D(u_colormap, vec2(v_dist,.5)).rgb;\n",
+      "    gl_FragColor = vec4(color,(1-a)*.25);\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Galaxy(app.Canvas):\n",
+      "    \n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)\n",
+      "        self.view = np.eye(4, dtype=np.float32)\n",
+      "        self.model = np.eye(4, dtype=np.float32)\n",
+      "        self.projection = np.eye(4, dtype=np.float32)\n",
+      "        self.theta, self.phi = 0, 0\n",
+      "\n",
+      "        self.translate = 5\n",
+      "        translate(self.view, 0, 0, -self.translate)\n",
+      "\n",
+      "        self.program.bind(gloo.VertexBuffer(data))\n",
+      "        self.program['u_colormap'] = gloo.Texture2D(cmap)\n",
+      "        self.program['u_size'] = 5. / self.translate\n",
+      "        self.program['u_model'] = self.model\n",
+      "        self.program['u_view'] = self.view\n",
+      "        \n",
+      "        self._timer = app.Timer(1.0 / 60, connect=self.on_timer, start=True)\n",
+      "\n",
+      "    def on_initialize(self, event):\n",
+      "        gloo.set_state(depth_test=False, blend=True,\n",
+      "                       blend_func=('src_alpha', 'one'),\n",
+      "                       clear_color=(0, 0, 0, 1))\n",
+      "\n",
+      "    def on_timer(self, event):\n",
+      "        self.theta += .11\n",
+      "        self.phi += .13\n",
+      "        self.model = np.eye(4, dtype=np.float32)\n",
+      "        rotate(self.model, self.theta, 0, 0, 1)\n",
+      "        rotate(self.model, self.phi, 0, 1, 0)\n",
+      "        self.program['u_model'] = self.model\n",
+      "        self.update()\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        width, height = event.size\n",
+      "        gloo.set_viewport(0, 0, width, height)\n",
+      "        self.projection = perspective(45.0, width / float(height), 1.0, 1000.0)\n",
+      "        self.program['u_projection'] = self.projection\n",
+      "\n",
+      "    def on_mouse_wheel(self, event):\n",
+      "        self.translate += event.delta[1]\n",
+      "        self.translate = max(2, self.translate)\n",
+      "        self.view = np.eye(4, dtype=np.float32)\n",
+      "        translate(self.view, 0, 0, -self.translate)\n",
+      "        self.program['u_view'] = self.view\n",
+      "        self.program['u_size'] = 5 / self.translate\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        gloo.clear()\n",
+      "        self.program.draw('points')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g = Galaxy()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "g.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/mandelbrot.ipynb b/examples/ipynb/mandelbrot.ipynb
new file mode 100644
index 0000000..187a591
--- /dev/null
+++ b/examples/ipynb/mandelbrot.ipynb
@@ -0,0 +1,251 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:6f42d4861c051c27386729397f659e04aee2048c30ffad0239cc7c79f8cc9e50"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "from vispy import app, use, gloo"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "vertex = \"\"\"\n",
+      "attribute vec2 position;\n",
+      "\n",
+      "void main()\n",
+      "{\n",
+      "    gl_Position = vec4(position, 0, 1.0);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "fragment = \"\"\"\n",
+      "uniform vec2 resolution;\n",
+      "uniform vec2 center;\n",
+      "uniform float scale;\n",
+      "uniform int iter;\n",
+      "\n",
+      "// Jet color scheme\n",
+      "vec4 color_scheme(float x) {\n",
+      "    vec3 a, b;\n",
+      "    float c;\n",
+      "    if (x < 0.34) {\n",
+      "        a = vec3(0, 0, 0.5);\n",
+      "        b = vec3(0, 0.8, 0.95);\n",
+      "        c = (x - 0.0) / (0.34 - 0.0);\n",
+      "    } else if (x < 0.64) {\n",
+      "        a = vec3(0, 0.8, 0.95);\n",
+      "        b = vec3(0.85, 1, 0.04);\n",
+      "        c = (x - 0.34) / (0.64 - 0.34);\n",
+      "    } else if (x < 0.89) {\n",
+      "        a = vec3(0.85, 1, 0.04);\n",
+      "        b = vec3(0.96, 0.7, 0);\n",
+      "        c = (x - 0.64) / (0.89 - 0.64);\n",
+      "    } else {\n",
+      "        a = vec3(0.96, 0.7, 0);\n",
+      "        b = vec3(0.5, 0, 0);\n",
+      "        c = (x - 0.89) / (1.0 - 0.89);\n",
+      "    }\n",
+      "    return vec4(mix(a, b, c), 1.0);\n",
+      "}\n",
+      "\n",
+      "void main() {\n",
+      "    vec2 z, c;\n",
+      "\n",
+      "    // Recover coordinates from pixel coordinates\n",
+      "    c.x = (gl_FragCoord.x / resolution.x - 0.5) * scale + center.x;\n",
+      "    c.y = (gl_FragCoord.y / resolution.y - 0.5) * scale + center.y;\n",
+      "\n",
+      "    // Main Mandelbrot computation\n",
+      "    int i;\n",
+      "    z = c;\n",
+      "    for(i = 0; i < iter; i++) {\n",
+      "        float x = (z.x * z.x - z.y * z.y) + c.x;\n",
+      "        float y = (z.y * z.x + z.x * z.y) + c.y;\n",
+      "\n",
+      "        if((x * x + y * y) > 4.0) break;\n",
+      "        z.x = x;\n",
+      "        z.y = y;\n",
+      "    }\n",
+      "\n",
+      "    // Convert iterations to color\n",
+      "    float color = 1.0 - float(i) / float(iter);\n",
+      "    gl_FragColor = color_scheme(color);\n",
+      "\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Mandelbrot(app.Canvas):\n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "        self.program = gloo.Program(vertex, fragment)\n",
+      "\n",
+      "        # Draw a rectangle that takes up the whole screen. All of the work is\n",
+      "        # done in the shader.\n",
+      "        self.program[\"position\"] = [(-1, -1), (-1, 1), (1, 1),\n",
+      "                                    (-1, -1), (1, 1), (1, -1)]\n",
+      "\n",
+      "        self.scale = self.program[\"scale\"] = 3\n",
+      "        self.center = self.program[\"center\"] = [-0.5, 0]\n",
+      "        self.iterations = self.program[\"iter\"] = 300\n",
+      "        self.program['resolution'] = self.size\n",
+      "\n",
+      "        self.bounds = [-2, 2]\n",
+      "        self.min_scale = 0.00005\n",
+      "        self.max_scale = 4\n",
+      "        \n",
+      "    def on_draw(self, event):\n",
+      "        self.program.draw()\n",
+      "        self.update()\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        width, height = event.size\n",
+      "        gloo.set_viewport(0, 0, width, height)\n",
+      "        self.program['resolution'] = [width, height]\n",
+      "\n",
+      "    def on_mouse_move(self, event):\n",
+      "        \"\"\"Pan the view based on the change in mouse position.\"\"\"\n",
+      "        if event.is_dragging and event.button == 0:\n",
+      "            x0, y0 = event.last_event.pos[0], event.last_event.pos[1]\n",
+      "            x1, y1 = event.pos[0], event.pos[1]\n",
+      "            X0, Y0 = self.pixel_to_coords(float(x0), float(y0))\n",
+      "            X1, Y1 = self.pixel_to_coords(float(x1), float(y1))\n",
+      "            self.translate_center(X1 - X0, Y1 - Y0)\n",
+      "        \n",
+      "    def translate_center(self, dx, dy):\n",
+      "        \"\"\"Translates the center point, and keeps it in bounds.\"\"\"\n",
+      "        center = self.center\n",
+      "        center[0] -= dx\n",
+      "        center[1] -= dy\n",
+      "        center[0] = min(max(center[0], self.bounds[0]), self.bounds[1])\n",
+      "        center[1] = min(max(center[1], self.bounds[0]), self.bounds[1])\n",
+      "        self.program[\"center\"] = self.center = center\n",
+      "\n",
+      "    def pixel_to_coords(self, x, y):\n",
+      "        \"\"\"Convert pixel coordinates to Mandelbrot set coordinates.\"\"\"\n",
+      "        rx, ry = self.size\n",
+      "        nx = (x / rx - 0.5) * self.scale + self.center[0]\n",
+      "        ny = ((ry - y) / ry - 0.5) * self.scale + self.center[1]\n",
+      "        return [nx, ny]\n",
+      "\n",
+      "    def on_mouse_wheel(self, event):\n",
+      "        \"\"\"Use the mouse wheel to zoom.\"\"\"\n",
+      "        delta = event.delta[1]\n",
+      "        if delta > 0:  # Zoom in\n",
+      "            factor = 0.9\n",
+      "        elif delta < 0:  # Zoom out\n",
+      "            factor = 1 / 0.9\n",
+      "        for _ in range(int(abs(delta))):\n",
+      "            self.zoom(factor, event.pos)\n",
+      "        \n",
+      "    def on_key_press(self, event):\n",
+      "        \"\"\"Use A or Z to zoom in and out.\n",
+      "\n",
+      "        The mouse wheel can be used to zoom, but some people don't have mouse\n",
+      "        wheels :)\n",
+      "\n",
+      "        \"\"\"\n",
+      "        if event.text == 'A':\n",
+      "            self.zoom(0.9)\n",
+      "        elif event.text == 'Z':\n",
+      "            self.zoom(1/0.9)    \n",
+      "        \n",
+      "    def zoom(self, factor, mouse_coords=None):\n",
+      "        \"\"\"Factors less than zero zoom in, and greater than zero zoom out.\n",
+      "\n",
+      "        If mouse_coords is given, the point under the mouse stays stationary\n",
+      "        while zooming. mouse_coords should come from MouseEvent.pos.\n",
+      "\n",
+      "        \"\"\"\n",
+      "        if mouse_coords:  # Record the position of the mouse\n",
+      "            x, y = float(mouse_coords[0]), float(mouse_coords[1])\n",
+      "            x0, y0 = self.pixel_to_coords(x, y)\n",
+      "\n",
+      "        self.scale *= factor\n",
+      "        self.scale = max(min(self.scale, self.max_scale), self.min_scale)\n",
+      "        self.program[\"scale\"] = self.scale\n",
+      "\n",
+      "        if mouse_coords:  # Translate so the mouse point is stationary\n",
+      "            x1, y1 = self.pixel_to_coords(x, y)\n",
+      "            self.translate_center(x1 - x0, y1 - y0)\n"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "m = Mandelbrot(size=(800, 600))"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "m.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Resize example\n",
+      "m.size = (400, 300)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "m.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/post_processing.ipynb b/examples/ipynb/post_processing.ipynb
new file mode 100644
index 0000000..9ec9c1e
--- /dev/null
+++ b/examples/ipynb/post_processing.ipynb
@@ -0,0 +1,228 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:6863b118647effae2133dbddd0370cb718493d0b8c580ab031e464b441de0e7f"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import numpy as np\n",
+      "from vispy import app, use\n",
+      "\n",
+      "from vispy.geometry import create_cube\n",
+      "from vispy.util.transforms import perspective, translate, rotate\n",
+      "from vispy.gloo import (Program, VertexBuffer, IndexBuffer, Texture2D, clear,\n",
+      "                        FrameBuffer, DepthBuffer, set_viewport, set_state)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "cube_vertex = \"\"\"\n",
+      "uniform mat4 model;\n",
+      "uniform mat4 view;\n",
+      "uniform mat4 projection;\n",
+      "attribute vec3 position;\n",
+      "attribute vec2 texcoord;\n",
+      "attribute vec3 normal;\n",
+      "attribute vec4 color;\n",
+      "varying vec2 v_texcoord;\n",
+      "void main()\n",
+      "{\n",
+      "    gl_Position = projection * view * model * vec4(position,1.0);\n",
+      "    v_texcoord = texcoord;\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "cube_fragment = \"\"\"\n",
+      "uniform sampler2D texture;\n",
+      "varying vec2 v_texcoord;\n",
+      "void main()\n",
+      "{\n",
+      "    float r = texture2D(texture, v_texcoord).r;\n",
+      "    gl_FragColor = vec4(r,r,r,1);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "quad_vertex = \"\"\"\n",
+      "attribute vec2 position;\n",
+      "attribute vec2 texcoord;\n",
+      "varying vec2 v_texcoord;\n",
+      "void main()\n",
+      "{\n",
+      "    gl_Position = vec4(position, 0.0, 1.0);\n",
+      "    v_texcoord = texcoord;\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "quad_fragment = \"\"\"\n",
+      "uniform sampler2D texture;\n",
+      "varying vec2 v_texcoord;\n",
+      "void main()\n",
+      "{\n",
+      "    vec2 d = 5.0 * vec2(sin(v_texcoord.y*50.0),0)/512.0;\n",
+      "\n",
+      "    // Inverse video\n",
+      "    if( v_texcoord.x > 0.5 ) {\n",
+      "        gl_FragColor.rgb = 1.0-texture2D(texture, v_texcoord+d).rgb;\n",
+      "    } else {\n",
+      "        gl_FragColor = texture2D(texture, v_texcoord);\n",
+      "    }\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def checkerboard(grid_num=8, grid_size=32):\n",
+      "    row_even = grid_num // 2 * [0, 1]\n",
+      "    row_odd = grid_num // 2 * [1, 0]\n",
+      "    Z = np.row_stack(grid_num // 2 * (row_even, row_odd)).astype(np.uint8)\n",
+      "    return 255 * Z.repeat(grid_size, axis=0).repeat(grid_size, axis=1)"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Canvas(app.Canvas):\n",
+      "\n",
+      "    def __init__(self):\n",
+      "        app.Canvas.__init__(self, title='Framebuffer post-processing',\n",
+      "                            size=(512, 512))\n",
+      "        self._timer = app.Timer(1.0 / 60, connect=self.on_timer, start=True)\n",
+      "\n",
+      "    def on_initialize(self, event):\n",
+      "        # Build cube data\n",
+      "        # --------------------------------------\n",
+      "        vertices, indices, _ = create_cube()\n",
+      "        vertices = VertexBuffer(vertices)\n",
+      "        self.indices = IndexBuffer(indices)\n",
+      "\n",
+      "        # Build program\n",
+      "        # --------------------------------------\n",
+      "        view = np.eye(4, dtype=np.float32)\n",
+      "        model = np.eye(4, dtype=np.float32)\n",
+      "        translate(view, 0, 0, -7)\n",
+      "        self.phi, self.theta = 60, 20\n",
+      "        rotate(model, self.theta, 0, 0, 1)\n",
+      "        rotate(model, self.phi, 0, 1, 0)\n",
+      "\n",
+      "        self.cube = Program(cube_vertex, cube_fragment)\n",
+      "        self.cube.bind(vertices)\n",
+      "        self.cube[\"texture\"] = checkerboard()\n",
+      "        self.cube[\"texture\"].interpolation = 'linear'\n",
+      "        self.cube['model'] = model\n",
+      "        self.cube['view'] = view\n",
+      "\n",
+      "        depth = DepthBuffer((512, 512))\n",
+      "        color = Texture2D(shape=(512, 512, 3), dtype=np.dtype(np.float32))\n",
+      "        self.framebuffer = FrameBuffer(color=color, depth=depth)\n",
+      "\n",
+      "        self.quad = Program(quad_vertex, quad_fragment, count=4)\n",
+      "        self.quad['texcoord'] = [(0, 0), (0, 1), (1, 0), (1, 1)]\n",
+      "        self.quad['position'] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]\n",
+      "        self.quad['texture'] = color\n",
+      "        self.quad[\"texture\"].interpolation = 'linear'\n",
+      "\n",
+      "        # OpenGL and Timer initalization\n",
+      "        # --------------------------------------\n",
+      "        set_state(clear_color=(.3, .3, .35, 1), depth_test=True)\n",
+      "        self._set_projection(self.size)\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        self.framebuffer.activate()\n",
+      "        set_viewport(0, 0, 512, 512)\n",
+      "        clear(color=True, depth=True)\n",
+      "        set_state(depth_test=True)\n",
+      "        self.cube.draw('triangles', self.indices)\n",
+      "        self.framebuffer.deactivate()\n",
+      "        clear(color=True)\n",
+      "        set_state(depth_test=False)\n",
+      "        self.quad.draw('triangle_strip')\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        self._set_projection(event.size)\n",
+      "\n",
+      "    def _set_projection(self, size):\n",
+      "        width, height = size\n",
+      "        set_viewport(0, 0, width, height)\n",
+      "        projection = perspective(30.0, width / float(height), 2.0, 10.0)\n",
+      "        self.cube['projection'] = projection\n",
+      "\n",
+      "    def on_timer(self, event):\n",
+      "        self.theta += .5\n",
+      "        self.phi += .5\n",
+      "        model = np.eye(4, dtype=np.float32)\n",
+      "        rotate(model, self.theta, 0, 0, 1)\n",
+      "        rotate(model, self.phi, 0, 1, 0)\n",
+      "        self.cube['model'] = model\n",
+      "        self.update()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "c = Canvas()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "c.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "c.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
diff --git a/examples/ipynb/rain.ipynb b/examples/ipynb/rain.ipynb
new file mode 100644
index 0000000..fa85e75
--- /dev/null
+++ b/examples/ipynb/rain.ipynb
@@ -0,0 +1,202 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:6520e892443ed78df97922e18a8dfebc72d4c0408ead6302ef3e63b4bc194eee"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import numpy as np\n",
+      "\n",
+      "from vispy import gloo, app, use\n",
+      "from vispy.gloo import Program, VertexBuffer\n",
+      "from vispy.util.transforms import ortho"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "vertex = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "uniform mat4  u_model;\n",
+      "uniform mat4  u_view;\n",
+      "uniform mat4  u_projection;\n",
+      "uniform float u_linewidth;\n",
+      "uniform float u_antialias;\n",
+      "\n",
+      "attribute vec3  a_position;\n",
+      "attribute vec4  a_fg_color;\n",
+      "attribute float a_size;\n",
+      "\n",
+      "varying vec4  v_fg_color;\n",
+      "varying float v_size;\n",
+      "\n",
+      "void main (void)\n",
+      "{\n",
+      "    v_size = a_size;\n",
+      "    v_fg_color = a_fg_color;\n",
+      "    if( a_fg_color.a > 0.0)\n",
+      "    {\n",
+      "        gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);\n",
+      "        gl_PointSize = v_size + u_linewidth + 2*1.5*u_antialias;\n",
+      "    }\n",
+      "    else\n",
+      "    {\n",
+      "        gl_Position = u_projection * u_view * u_model * vec4(-1,-1,0,1);\n",
+      "        gl_PointSize = 0.0;\n",
+      "    }\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "fragment = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "uniform float u_linewidth;\n",
+      "uniform float u_antialias;\n",
+      "varying vec4  v_fg_color;\n",
+      "varying vec4  v_bg_color;\n",
+      "varying float v_size;\n",
+      "float disc(vec2 P, float size)\n",
+      "{\n",
+      "    return length((P.xy - vec2(0.5,0.5))*size);\n",
+      "}\n",
+      "void main()\n",
+      "{\n",
+      "    if( v_fg_color.a <= 0.0)\n",
+      "        discard;\n",
+      "    float actual_size = v_size + u_linewidth + 2*1.5*u_antialias;\n",
+      "    float t = u_linewidth/2.0 - u_antialias;\n",
+      "    float r = disc(gl_PointCoord, actual_size);\n",
+      "    float d = abs(r - v_size/2.0) - t;\n",
+      "    if( d < 0.0 )\n",
+      "    {\n",
+      "         gl_FragColor = v_fg_color;\n",
+      "    }\n",
+      "    else if( abs(d) > 2.5*u_antialias )\n",
+      "    {\n",
+      "         discard;\n",
+      "    }\n",
+      "    else\n",
+      "    {\n",
+      "        d /= u_antialias;\n",
+      "        gl_FragColor = vec4(v_fg_color.rgb, exp(-d*d)*v_fg_color.a);\n",
+      "    }\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Rain(app.Canvas):\n",
+      "    def __init__(self):\n",
+      "        app.Canvas.__init__(self, title='Rain [Move mouse]',\n",
+      "                            size=(512, 512))\n",
+      "        self._timer = app.Timer(1.0 / 60, connect=self.on_timer, start=True)\n",
+      "\n",
+      "    def on_initialize(self, event):\n",
+      "        # Build data\n",
+      "        # --------------------------------------\n",
+      "        n = 500\n",
+      "        self.data = np.zeros(n, [('a_position', np.float32, 2),\n",
+      "                                 ('a_fg_color', np.float32, 4),\n",
+      "                                 ('a_size',     np.float32, 1)])\n",
+      "        self.index = 0\n",
+      "        self.program = Program(vertex, fragment)\n",
+      "        self.vdata = VertexBuffer(self.data)\n",
+      "        self.program.bind(self.vdata)\n",
+      "        self.program['u_antialias'] = 1.00\n",
+      "        self.program['u_linewidth'] = 1.00\n",
+      "        self.program['u_model'] = np.eye(4, dtype=np.float32)\n",
+      "        self.program['u_view'] = np.eye(4, dtype=np.float32)\n",
+      "        gloo.set_clear_color('white')\n",
+      "        gloo.set_state(blend=True,\n",
+      "                       blend_func=('src_alpha', 'one_minus_src_alpha'))\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        gloo.clear()\n",
+      "        self.program.draw('points')\n",
+      "        self.update()\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        gloo.set_viewport(0, 0, *event.size)\n",
+      "        projection = ortho(0, event.size[0], 0, event.size[1], -1, +1)\n",
+      "        self.program['u_projection'] = projection\n",
+      "\n",
+      "    def on_timer(self, event):\n",
+      "        self.data['a_fg_color'][..., 3] -= 0.01\n",
+      "        self.data['a_size'] += 1.0\n",
+      "        self.vdata.set_data(self.data)\n",
+      "\n",
+      "    def on_mouse_move(self, event):\n",
+      "        x, y = event.pos\n",
+      "        h = gloo.get_parameter('viewport')[3]\n",
+      "        self.data['a_position'][self.index] = x, h - y\n",
+      "        self.data['a_size'][self.index] = 5\n",
+      "        self.data['a_fg_color'][self.index] = 0, 0, 0, 1\n",
+      "        self.index = (self.index + 1) % 500"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "r = Rain()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "r.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "r.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/spacy.ipynb b/examples/ipynb/spacy.ipynb
new file mode 100644
index 0000000..ab41f82
--- /dev/null
+++ b/examples/ipynb/spacy.ipynb
@@ -0,0 +1,220 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:7339f421d784141cfef90fc1d87b21e83beb837c211535f685cb9c8b7d0d2d17"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import time\n",
+      "\n",
+      "import numpy as np\n",
+      "\n",
+      "from vispy import gloo\n",
+      "from vispy import app, use\n",
+      "from vispy.util.transforms import perspective"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "vertex = \"\"\"\n",
+      "#version 120\n",
+      "\n",
+      "uniform mat4 u_model;\n",
+      "uniform mat4 u_view;\n",
+      "uniform mat4 u_projection;\n",
+      "uniform float u_time_offset;\n",
+      "\n",
+      "attribute vec3  a_position;\n",
+      "attribute float a_offset;\n",
+      "\n",
+      "varying float v_pointsize;\n",
+      "\n",
+      "void main (void) {\n",
+      "   \n",
+      "    vec3 pos = a_position;\n",
+      "    pos.z = pos.z - a_offset - u_time_offset;\n",
+      "    vec4 v_eye_position = u_view * u_model * vec4(pos, 1.0);\n",
+      "    gl_Position = u_projection * v_eye_position;\n",
+      "\n",
+      "    // stackoverflow.com/questions/8608844/...\n",
+      "    //  ... resizing-point-sprites-based-on-distance-from-the-camera\n",
+      "    float radius = 1;\n",
+      "    vec4 corner = vec4(radius, radius, v_eye_position.z, v_eye_position.w);\n",
+      "    vec4  proj_corner = u_projection * corner;\n",
+      "    gl_PointSize = 100.0 * proj_corner.x / proj_corner.w;\n",
+      "    v_pointsize = gl_PointSize;\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "fragment = \"\"\"\n",
+      "#version 120\n",
+      "varying float v_pointsize;\n",
+      "void main()\n",
+      "{\n",
+      "    float x = 2.0*gl_PointCoord.x - 1.0;\n",
+      "    float y = 2.0*gl_PointCoord.y - 1.0;\n",
+      "    float a = 0.9 - (x*x + y*y);\n",
+      "    a = a * min(1.0, v_pointsize/1.5);\n",
+      "    gl_FragColor = vec4(1.0, 1.0, 1.0, a);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "N = 10000  # Number of stars \n",
+      "SIZE = 100\n",
+      "SPEED = 4.0  # time in seconds to go through one block\n",
+      "NBLOCKS = 10\n"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Space(app.Canvas):\n",
+      "\n",
+      "    def __init__(self, *args, **kwargs):\n",
+      "        app.Canvas.__init__(self, *args, **kwargs)\n",
+      "        \n",
+      "        self.program = gloo.Program(vertex, fragment)\n",
+      "        self.view = np.eye(4, dtype=np.float32)\n",
+      "        self.model = np.eye(4, dtype=np.float32)\n",
+      "        self.projection = np.eye(4, dtype=np.float32)\n",
+      "                \n",
+      "        # Set uniforms (some are set later)\n",
+      "        self.program['u_model'] = self.model\n",
+      "        self.program['u_view'] = self.view\n",
+      "        \n",
+      "        # Set attributes\n",
+      "        self.program['a_position'] = np.zeros((N, 3), np.float32)\n",
+      "        self.program['a_offset'] = np.zeros((N,), np.float32)\n",
+      "        \n",
+      "        # Init\n",
+      "        self._timeout = 0\n",
+      "        self._active_block = 0\n",
+      "        for i in range(NBLOCKS):\n",
+      "            self._generate_stars()\n",
+      "        self._timeout = time.time() + SPEED\n",
+      "        \n",
+      "        self._timer = app.Timer(1.0 / 60, connect=self.on_timer, start=True)\n",
+      "    \n",
+      "    def on_initialize(self, event):\n",
+      "        gloo.set_state(clear_color=(0.0, 0.0, 0.0, 0.0), depth_test=False,\n",
+      "                       blend=True, blend_equation='func_add',\n",
+      "                       blend_func=('src_alpha', 'one_minus_src_alpha'))\n",
+      "        \n",
+      "    def on_timer(self, event):\n",
+      "        self.update()\n",
+      "        \n",
+      "    def on_resize(self, event):\n",
+      "        width, height = event.size\n",
+      "        gloo.set_viewport(0, 0, width, height)\n",
+      "        far = SIZE*(NBLOCKS-2)\n",
+      "        self.projection = perspective(25.0, width / float(height), 1.0, far)\n",
+      "        self.program['u_projection'] = self.projection\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        # Set time offset. Factor runs from 1 to 0\n",
+      "        # the time offset goes from 0 to size\n",
+      "        factor = (self._timeout - time.time()) / SPEED\n",
+      "        self.program['u_time_offset'] = -(1-factor) * SIZE\n",
+      "        \n",
+      "        # Draw\n",
+      "        gloo.clear(color=(0., 0., 0., 1.))\n",
+      "        self.program.draw('points')\n",
+      "        \n",
+      "        # Build new starts if the first block is fully behind us\n",
+      "        if factor < 0:\n",
+      "            self._generate_stars()\n",
+      "    \n",
+      "    def _generate_stars(self):\n",
+      "        \n",
+      "        # Get number of stars in each block\n",
+      "        blocksize = N // NBLOCKS\n",
+      "        \n",
+      "        # Update active block\n",
+      "        self._active_block += 1\n",
+      "        if self._active_block >= NBLOCKS:\n",
+      "            self._active_block = 0\n",
+      "        \n",
+      "        # Create new position data for the active block\n",
+      "        pos = np.zeros((blocksize, 3), 'float32') \n",
+      "        pos[:, :2] = np.random.normal(0.0, SIZE/2, (blocksize, 2))  # x-y\n",
+      "        pos[:, 2] = np.random.uniform(0, SIZE, (blocksize,))  # z\n",
+      "        start_index = self._active_block * blocksize\n",
+      "        self.program['a_position'].set_data(pos, offset=start_index) \n",
+      "        \n",
+      "        # Set offsets - active block gets offset 0\n",
+      "        for i in range(NBLOCKS):\n",
+      "            val = i - self._active_block\n",
+      "            if val < 0:\n",
+      "                val += NBLOCKS\n",
+      "            values = np.ones((blocksize, 1), 'float32') * val * SIZE\n",
+      "            start_index = i*blocksize\n",
+      "            self.program['a_offset'].set_data(values, offset=start_index) \n",
+      "        \n",
+      "        # Reset timer\n",
+      "        self._timeout += SPEED"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "s = Space()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "s.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "s.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/ipynb/voronoi.ipynb b/examples/ipynb/voronoi.ipynb
new file mode 100644
index 0000000..2920fb2
--- /dev/null
+++ b/examples/ipynb/voronoi.ipynb
@@ -0,0 +1,172 @@
+{
+ "metadata": {
+  "name": "",
+  "signature": "sha256:c3963f2cba5f56be4cccbc9e47bc9593f1b72934b7298603c51400dfb528661d"
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "import numpy as np\n",
+      "\n",
+      "from vispy import app, use\n",
+      "from vispy import gloo"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "use('ipynb_vnc')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "# Voronoi shaders.\n",
+      "VS_voronoi = \"\"\"\n",
+      "attribute vec2 a_position;\n",
+      "\n",
+      "void main() {\n",
+      "    gl_Position = vec4(a_position, 0., 1.);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "FS_voronoi = \"\"\"\n",
+      "uniform vec2 u_seeds[32];\n",
+      "uniform vec3 u_colors[32];\n",
+      "uniform vec2 u_screen;\n",
+      "\n",
+      "void main() {\n",
+      "    float dist = distance(u_screen * u_seeds[0], gl_FragCoord.xy);\n",
+      "    vec3 color = u_colors[0];\n",
+      "    for (int i = 1; i < 32; i++) {\n",
+      "        float current = distance(u_screen * u_seeds[i], gl_FragCoord.xy);\n",
+      "        if (current < dist) {\n",
+      "            color = u_colors[i];\n",
+      "            dist = current;\n",
+      "        }\n",
+      "    }\n",
+      "    gl_FragColor = vec4(color, 1.0);\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "\n",
+      "# Seed point shaders.\n",
+      "VS_seeds = \"\"\"\n",
+      "attribute vec2 a_position;\n",
+      "\n",
+      "void main() {\n",
+      "    gl_Position = vec4(2. * a_position - 1., 0., 1.);\n",
+      "    gl_PointSize = 10.;\n",
+      "}\n",
+      "\"\"\"\n",
+      "\n",
+      "FS_seeds = \"\"\"\n",
+      "varying vec3 v_color;\n",
+      "void main() {\n",
+      "    gl_FragColor = vec4(1., 1., 1., 1.);\n",
+      "}\n",
+      "\"\"\""
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "class Voronoi(app.Canvas):\n",
+      "    def __init__(self):\n",
+      "        app.Canvas.__init__(self, size=(600, 600))\n",
+      "        \n",
+      "        self.seeds = np.random.uniform(0, 1,\n",
+      "                                       size=(32, 2)).astype(np.float32)\n",
+      "        self.colors = np.random.uniform(0.3, 0.8, \n",
+      "                                        size=(32, 3)).astype(np.float32)\n",
+      "        \n",
+      "        # Set Voronoi program.\n",
+      "        self.program_v = gloo.Program(VS_voronoi, FS_voronoi)\n",
+      "        self.program_v['a_position'] = [(-1, -1), (-1, +1), (+1, -1), (+1, +1)]\n",
+      "        # HACK: work-around a bug related to uniform arrays until \n",
+      "        # issue #345 is solved.\n",
+      "        for i in range(32):\n",
+      "            self.program_v['u_seeds[%d]' % i] = self.seeds[i, :]\n",
+      "            self.program_v['u_colors[%d]' % i] = self.colors[i, :]\n",
+      "            \n",
+      "        # Set seed points program.\n",
+      "        self.program_s = gloo.Program(VS_seeds, FS_seeds)\n",
+      "        self.program_s['a_position'] = self.seeds\n",
+      "\n",
+      "    def on_draw(self, event):\n",
+      "        gloo.clear()\n",
+      "        self.program_v.draw('triangle_strip')\n",
+      "        self.program_s.draw('points')\n",
+      "\n",
+      "    def on_resize(self, event):\n",
+      "        self.width, self.height = event.size\n",
+      "        gloo.set_viewport(0, 0, self.width, self.height)\n",
+      "        self.program_v['u_screen'] = (self.width, self.height)\n",
+      "        \n",
+      "    def on_mouse_move(self, event):\n",
+      "        x, y = event.pos\n",
+      "        x, y = x/float(self.width), 1-y/float(self.height)\n",
+      "        self.program_v['u_seeds[0]'] = x, y\n",
+      "        # TODO: just update the first line in the VBO instead of uploading the\n",
+      "        # whole array of seed points.\n",
+      "        self.seeds[0, :] = x, y\n",
+      "        self.program_s['a_position'].set_data(self.seeds)\n",
+      "        self.update()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "v = Voronoi()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "v.show()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "v.close()"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": []
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/examples/rawgl/rawgl-cube.py b/examples/rawgl/rawgl-cube.py
deleted file mode 100644
index a01f0cc..0000000
--- a/examples/rawgl/rawgl-cube.py
+++ /dev/null
@@ -1,202 +0,0 @@
-#!/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
deleted file mode 100644
index ae25782..0000000
--- a/examples/rawgl/rawgl-fireworks.py
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/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
deleted file mode 100644
index db9cf56..0000000
--- a/examples/spinning-cube2.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/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
deleted file mode 100644
index 408bc6c..0000000
--- a/examples/texturing.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# #!/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/examples/app/app-event.py b/examples/tutorial/app/app_events.py
similarity index 70%
rename from examples/app/app-event.py
rename to examples/tutorial/app/app_events.py
index 0433c83..1e0474a 100644
--- a/examples/app/app-event.py
+++ b/examples/tutorial/app/app_events.py
@@ -1,62 +1,64 @@
-# #!/usr/bin/env python
 # -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
 """
 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
+from vispy import gloo, app, use
+use('pyqt4')  # can be another app backend name
+
 
 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))
-    
+            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))
-    
+            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):
+        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' %
+        print('%s - pos: %r, button: %s, 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)
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
 
 
 if __name__ == '__main__':
-    canvas = Canvas()
+    canvas = Canvas(keys='interactive')
     canvas.show()
     app.run()
diff --git a/examples/app/simple.py b/examples/tutorial/app/fps.py
similarity index 53%
copy from examples/app/simple.py
copy to examples/tutorial/app/fps.py
index 2eff1d6..c7daf55 100644
--- a/examples/app/simple.py
+++ b/examples/tutorial/app/fps.py
@@ -1,7 +1,6 @@
-# #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# vispy: gallery 20
-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
 """
 This is a very minimal example that opens a window and makes the background
 color to change from black to white to black ...
@@ -11,27 +10,30 @@ on what is available on your machine.
 """
 
 import math
-from vispy import app
-from vispy.gloo import gl
+from vispy import app, gloo
+
 
 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._timer = app.Timer('auto', connect=self.on_timer, start=True)
         self.tick = 0
-        
-    def on_paint(self, event):
-        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
-        
+
+    def on_draw(self, event):
+        gloo.clear(color=True)
+
     def on_timer(self, event):
-        self.tick += 1/60.0
+        self.tick += 1 / 60.0
         c = abs(math.sin(self.tick))
-        gl.glClearColor(c,c,c,1)
+        gloo.set_clear_color((c, c, c, 1))
         self.update()
 
+    def show_fps(self, fps):
+        print("FPS - %.2f" % fps)
+
 if __name__ == '__main__':
-    canvas = Canvas()
+    canvas = Canvas(keys='interactive')
     canvas.show()
+    canvas.measure_fps(1, canvas.show_fps)
     app.run()
diff --git a/examples/tutorial/app/shared_context.py b/examples/tutorial/app/shared_context.py
new file mode 100644
index 0000000..8735519
--- /dev/null
+++ b/examples/tutorial/app/shared_context.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+This is a very simple example that demonstrates using a shared context
+between two Qt widgets.
+"""
+
+from PyQt4 import QtGui, QtCore  # can also use pyside
+from functools import partial
+
+from vispy.app import Timer
+from vispy.scene.visuals import Text
+from vispy.scene.widgets import ViewBox
+from vispy.scene import SceneCanvas
+
+
+def on_resize(canvas, vb, event):
+    vb.pos = 1, 1
+    vb.size = (canvas.size[0] - 2, canvas.size[1] - 2)
+
+
+class Window(QtGui.QWidget):
+    def __init__(self):
+        super(Window, self).__init__()
+        box = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight, self)
+        self.resize(500, 200)
+        self.setLayout(box)
+
+        self.canvas_0 = SceneCanvas(bgcolor='w')
+        self.vb_0 = ViewBox(parent=self.canvas_0.scene, bgcolor='r')
+        self.vb_0.camera.rect = -1, -1, 2, 2
+        self.canvas_0.events.initialize.connect(self.on_init)
+        self.canvas_0.events.resize.connect(partial(on_resize,
+                                                    self.canvas_0,
+                                                    self.vb_0))
+        box.addWidget(self.canvas_0.native)
+
+        # pass the context from the first canvas to the second
+        self.canvas_1 = SceneCanvas(bgcolor='w', context=self.canvas_0.context)
+        self.vb_1 = ViewBox(parent=self.canvas_1.scene, bgcolor='b')
+        self.vb_1.camera.rect = -1, -1, 2, 2
+        self.canvas_1.events.resize.connect(partial(on_resize,
+                                                    self.canvas_1,
+                                                    self.vb_1))
+        box.addWidget(self.canvas_1.native)
+
+        self.tick_count = 0
+        self.timer = Timer(interval=1., connect=self.on_timer, start=True)
+        self.setWindowTitle('Shared contexts')
+        self.show()
+
+    def on_init(self, event):
+        self.text = Text('Initialized', font_size=40.,
+                         parent=[self.vb_0.scene, self.vb_1.scene])
+
+    def on_timer(self, event):
+        self.tick_count += 1
+        self.text.text = 'Tick #%s' % self.tick_count
+        self.canvas_0.update()
+        self.canvas_1.update()
+
+    def keyPressEvent(self, event):
+        if event.key() == QtCore.Qt.Key_Escape:
+            self.close()
+        elif event.key() == QtCore.Qt.Key_F11:
+            self.showNormal() if self.isFullScreen() else self.showFullScreen()
+
+if __name__ == '__main__':
+    qt_app = QtGui.QApplication([])
+    ex = Window()
+    qt_app.exec_()
diff --git a/examples/app/simple.py b/examples/tutorial/app/simple.py
similarity index 59%
rename from examples/app/simple.py
rename to examples/tutorial/app/simple.py
index 2eff1d6..ad94d4a 100644
--- a/examples/app/simple.py
+++ b/examples/tutorial/app/simple.py
@@ -1,7 +1,6 @@
-# #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# vispy: gallery 20
-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
 """
 This is a very minimal example that opens a window and makes the background
 color to change from black to white to black ...
@@ -11,27 +10,26 @@ on what is available on your machine.
 """
 
 import math
-from vispy import app
-from vispy.gloo import gl
+from vispy import app, gloo
+
 
 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._timer = app.Timer('auto', connect=self.on_timer, start=True)
         self.tick = 0
-        
-    def on_paint(self, event):
-        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
-        
+
+    def on_draw(self, event):
+        gloo.clear(color=True)
+
     def on_timer(self, event):
-        self.tick += 1/60.0
+        self.tick += 1 / 60.0
         c = abs(math.sin(self.tick))
-        gl.glClearColor(c,c,c,1)
+        gloo.set_clear_color((c, c, c, 1))
         self.update()
 
 if __name__ == '__main__':
-    canvas = Canvas()
+    canvas = Canvas(keys='interactive')
     canvas.show()
     app.run()
diff --git a/examples/tutorial/gl/cube.py b/examples/tutorial/gl/cube.py
new file mode 100644
index 0000000..7556829
--- /dev/null
+++ b/examples/tutorial/gl/cube.py
@@ -0,0 +1,234 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+import math
+import ctypes
+import numpy as np
+
+from vispy import app
+from OpenGL import GL as gl
+
+
+def checkerboard(grid_num=8, grid_size=32):
+    row_even = grid_num / 2 * [0, 1]
+    row_odd = grid_num / 2 * [1, 0]
+    Z = np.row_stack(grid_num / 2 * (row_even, row_odd)).astype(np.uint8)
+    return 255 * Z.repeat(grid_size, axis=0).repeat(grid_size, axis=1)
+
+
+def rotate(M, angle, x, y, z, point=None):
+    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 = np.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]], dtype=M.dtype).T
+    M[...] = np.dot(M, R)
+    return M
+
+
+def translate(M, x, y=None, z=None):
+    y = x if y is None else y
+    z = x if z is None else z
+    T = np.array([[1.0, 0.0, 0.0, x],
+                  [0.0, 1.0, 0.0, y],
+                  [0.0, 0.0, 1.0, z],
+                  [0.0, 0.0, 0.0, 1.0]], dtype=M.dtype).T
+    M[...] = np.dot(M, T)
+    return M
+
+
+def frustum(left, right, bottom, top, 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):
+    h = math.tan(fovy / 360.0 * math.pi) * znear
+    w = h * aspect
+    return frustum(-w, w, -h, h, znear, zfar)
+
+
+def makecube():
+    """ Generate vertices & indices for a filled cube """
+
+    vtype = [('a_position', np.float32, 3),
+             ('a_texcoord', np.float32, 2)]
+    itype = np.uint32
+
+    # Vertices positions
+    p = np.array([[1, 1, 1], [-1, 1, 1], [-1, -1, 1], [1, -1, 1],
+                  [1, -1, -1], [1, 1, -1], [-1, 1, -1], [-1, -1, -1]])
+
+    # Texture coords
+    t = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
+
+    faces_p = [0, 1, 2, 3, 0, 3, 4, 5, 0, 5, 6,
+               1, 1, 6, 7, 2, 7, 4, 3, 2, 4, 7, 6, 5]
+    faces_t = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2,
+               3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
+
+    vertices = np.zeros(24, vtype)
+    vertices['a_position'] = p[faces_p]
+    vertices['a_texcoord'] = t[faces_t]
+
+    indices = np.resize(
+        np.array([0, 1, 2, 0, 2, 3], dtype=itype), 6 * (2 * 3))
+    indices += np.repeat(4 * np.arange(6), 6)
+
+    return vertices, indices
+
+
+cube_vertex = """
+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()
+{
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+    v_texcoord = a_texcoord;
+}
+"""
+
+cube_fragment = """
+uniform sampler2D u_texture;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_FragColor = texture2D(u_texture, v_texcoord);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512),
+                            title='Rotating cube (GL version)',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build & activate cube program
+        self.cube = gl.glCreateProgram()
+        vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+        fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+        gl.glShaderSource(vertex, cube_vertex)
+        gl.glShaderSource(fragment, cube_fragment)
+        gl.glCompileShader(vertex)
+        gl.glCompileShader(fragment)
+        gl.glAttachShader(self.cube, vertex)
+        gl.glAttachShader(self.cube, fragment)
+        gl.glLinkProgram(self.cube)
+        gl.glDetachShader(self.cube, vertex)
+        gl.glDetachShader(self.cube, fragment)
+        gl.glUseProgram(self.cube)
+
+        # Get data & build cube buffers
+        vcube_data, self.icube_data = makecube()
+        vcube = gl.glGenBuffers(1)
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vcube)
+        gl.glBufferData(gl.GL_ARRAY_BUFFER, vcube_data.nbytes,
+                        vcube_data, gl.GL_STATIC_DRAW)
+        icube = gl.glGenBuffers(1)
+        gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, icube)
+        gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER,
+                        self.icube_data.nbytes, self.icube_data,
+                        gl.GL_STATIC_DRAW)
+
+        # Bind cube attributes
+        stride = vcube_data.strides[0]
+        offset = ctypes.c_void_p(0)
+        loc = gl.glGetAttribLocation(self.cube, "a_position")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
+
+        offset = ctypes.c_void_p(vcube_data.dtype["a_position"].itemsize)
+        loc = gl.glGetAttribLocation(self.cube, "a_texcoord")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 2, gl.GL_FLOAT, False, stride, offset)
+
+        # Create & bind cube texture
+        crate = checkerboard()
+        texture = gl.glGenTextures(1)
+        gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER,
+                           gl.GL_LINEAR)
+        gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER,
+                           gl.GL_LINEAR)
+        gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S,
+                           gl.GL_CLAMP_TO_EDGE)
+        gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T,
+                           gl.GL_CLAMP_TO_EDGE)
+        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_INTENSITY,
+                        crate.shape[1], crate.shape[0],
+                        0, gl.GL_RED, gl.GL_UNSIGNED_BYTE, crate)
+        loc = gl.glGetUniformLocation(self.cube, "u_texture")
+        gl.glUniform1i(loc, texture)
+        gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
+
+        # Create & bind cube matrices
+        view = np.eye(4, dtype=np.float32)
+        model = np.eye(4, dtype=np.float32)
+        projection = np.eye(4, dtype=np.float32)
+        translate(view, 0, 0, -7)
+        self.phi, self.theta = 60, 20
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        loc = gl.glGetUniformLocation(self.cube, "u_model")
+        gl.glUniformMatrix4fv(loc, 1, False, model)
+        loc = gl.glGetUniformLocation(self.cube, "u_view")
+        gl.glUniformMatrix4fv(loc, 1, False, view)
+        loc = gl.glGetUniformLocation(self.cube, "u_projection")
+        gl.glUniformMatrix4fv(loc, 1, False, projection)
+
+        # OpenGL initalization
+        gl.glClearColor(0.30, 0.30, 0.35, 1.00)
+        gl.glEnable(gl.GL_DEPTH_TEST)
+
+        self.timer.start()
+
+    def on_draw(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        gl.glDrawElements(gl.GL_TRIANGLES, self.icube_data.size,
+                          gl.GL_UNSIGNED_INT, None)
+
+    def on_resize(self, event):
+        width, height = event.size
+        gl.glViewport(0, 0, width, height)
+        projection = perspective(35.0, width / float(height), 2.0, 10.0)
+        loc = gl.glGetUniformLocation(self.cube, "u_projection")
+        gl.glUniformMatrix4fv(loc, 1, False, projection)
+
+    def on_timer(self, event):
+        self.theta += .5
+        self.phi += .5
+        model = np.eye(4, dtype=np.float32)
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        loc = gl.glGetUniformLocation(self.cube, "u_model")
+        gl.glUniformMatrix4fv(loc, 1, False, model)
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gl/fireworks.py b/examples/tutorial/gl/fireworks.py
new file mode 100644
index 0000000..1c61150
--- /dev/null
+++ b/examples/tutorial/gl/fireworks.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author:   Almar Klein & Nicolas P .Rougier
+# Date:     04/03/2014
+# Topic:    Fireworks !
+# Keywords: oarticles, gl, sprites
+# -----------------------------------------------------------------------------
+"""
+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 ctypes
+import numpy as np
+import OpenGL.GL as gl
+
+from vispy import app
+
+
+vertex_code = """
+#version 120
+
+uniform float time;
+uniform vec3 center;
+attribute float lifetime;
+attribute vec3 start;
+attribute vec3 end;
+varying float v_lifetime;
+void main () {
+    if (time < lifetime) {
+        gl_Position.xyz = start + (time * end) + center;
+        gl_Position.w = 1.0;
+        gl_Position.y -= 1.5 * time * time;
+    } else {
+        gl_Position = vec4(-1000, -1000, 0, 0);
+    }
+    v_lifetime = clamp(1.0 - (time / lifetime), 0.0, 1.0);
+    gl_PointSize = (v_lifetime * v_lifetime) * 40.0;
+}
+"""
+
+fragment_code = """
+#version 120
+
+uniform vec4 color;
+varying float v_lifetime;
+void main()
+{
+    float d = 1 - length(gl_PointCoord - vec2(.5,.5)) / (sqrt(2)/2);
+    gl_FragColor = d*color;
+    gl_FragColor.a = d;
+    gl_FragColor.a *= v_lifetime;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(800, 600), title='GL Fireworks',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build & activate program
+        self.program = gl.glCreateProgram()
+        vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+        fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+        gl.glShaderSource(vertex, vertex_code)
+        gl.glShaderSource(fragment, fragment_code)
+        gl.glCompileShader(vertex)
+        gl.glCompileShader(fragment)
+        gl.glAttachShader(self.program, vertex)
+        gl.glAttachShader(self.program, fragment)
+        gl.glLinkProgram(self.program)
+        gl.glDetachShader(self.program, vertex)
+        gl.glDetachShader(self.program, fragment)
+        gl.glUseProgram(self.program)
+
+        # Build vertex buffer
+        n = 10000
+        self.data = np.zeros(n, dtype=[('lifetime', np.float32, 1),
+                                       ('start',    np.float32, 3),
+                                       ('end',      np.float32, 3)])
+        vbuffer = gl.glGenBuffers(1)
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbuffer)
+        gl.glBufferData(gl.GL_ARRAY_BUFFER, self.data.nbytes, self.data,
+                        gl.GL_DYNAMIC_DRAW)
+
+        # Bind buffer attributes
+        stride = self.data.strides[0]
+
+        offset = ctypes.c_void_p(0)
+        loc = gl.glGetAttribLocation(self.program, "lifetime")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 1, gl.GL_FLOAT, False, stride, offset)
+
+        offset = ctypes.c_void_p(self.data.dtype["lifetime"].itemsize)
+        loc = gl.glGetAttribLocation(self.program, "start")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
+
+        offset = ctypes.c_void_p(self.data.dtype["start"].itemsize)
+        loc = gl.glGetAttribLocation(self.program, "end")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
+
+        # OpenGL initalization
+        self.elapsed_time = 0
+        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)
+        gl.glEnable(gl.GL_VERTEX_PROGRAM_POINT_SIZE)
+        gl.glEnable(gl.GL_POINT_SPRITE)
+        self.new_explosion()
+        self.timer.start()
+
+    def on_draw(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+        gl.glDrawArrays(gl.GL_POINTS, 0, len(self.data))
+
+    def on_resize(self, event):
+        gl.glViewport(0, 0, *event.size)
+
+    def on_timer(self, event):
+        self.elapsed_time += 1. / 60.
+        if self.elapsed_time > 1.5:
+            self.new_explosion()
+            self.elapsed_time = 0.0
+
+        loc = gl.glGetUniformLocation(self.program, "time")
+        gl.glUniform1f(loc, self.elapsed_time)
+        self.update()
+
+    def new_explosion(self):
+        n = len(self.data)
+        color = np.random.uniform(0.1, 0.9, 4).astype(np.float32)
+        color[3] = 1.0 / n ** 0.08
+        loc = gl.glGetUniformLocation(self.program, "color")
+        gl.glUniform4f(loc, *color)
+
+        center = np.random.uniform(-0.5, 0.5, 3)
+        loc = gl.glGetUniformLocation(self.program, "center")
+        gl.glUniform3f(loc, *center)
+
+        self.data['lifetime'] = np.random.normal(2.0, 0.5, (n,))
+        self.data['start'] = np.random.normal(0.0, 0.2, (n, 3))
+        self.data['end'] = np.random.normal(0.0, 1.2, (n, 3))
+        gl.glBufferData(gl.GL_ARRAY_BUFFER, self.data.nbytes, self.data,
+                        gl.GL_DYNAMIC_DRAW)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gl/quad.py b/examples/tutorial/gl/quad.py
new file mode 100644
index 0000000..eee3fb6
--- /dev/null
+++ b/examples/tutorial/gl/quad.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+import ctypes
+import numpy as np
+import OpenGL.GL as gl
+
+from vispy import app
+
+vertex_code = """
+    uniform float scale;
+    attribute vec4 color;
+    attribute vec2 position;
+    varying vec4 v_color;
+    void main()
+    {
+        gl_Position = vec4(scale*position, 0.0, 1.0);
+        v_color = color;
+    } """
+
+fragment_code = """
+    varying vec4 v_color;
+    void main()
+    {
+        gl_FragColor = v_color;
+    } """
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Quad (GL)',
+                            keys='interactive')
+
+    def on_initialize(self, event):
+        # Build data
+        self.data = np.zeros(4, [("position", np.float32, 2),
+                                 ("color",    np.float32, 4)])
+        self.data['color'] = [(1, 0, 0, 1), (0, 1, 0, 1),
+                              (0, 0, 1, 1), (1, 1, 0, 1)]
+        self.data['position'] = [(-1, -1), (-1, +1),
+                                 (+1, -1), (+1, +1)]
+
+        # Build & activate program
+
+        # Request a program and shader slots from GPU
+        program = gl.glCreateProgram()
+        vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+        fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+
+        # Set shaders source
+        gl.glShaderSource(vertex, vertex_code)
+        gl.glShaderSource(fragment, fragment_code)
+
+        # Compile shaders
+        gl.glCompileShader(vertex)
+        gl.glCompileShader(fragment)
+
+        # Attach shader objects to the program
+        gl.glAttachShader(program, vertex)
+        gl.glAttachShader(program, fragment)
+
+        # Build program
+        gl.glLinkProgram(program)
+
+        # Get rid of shaders (no more needed)
+        gl.glDetachShader(program, vertex)
+        gl.glDetachShader(program, fragment)
+
+        # Make program the default program
+        gl.glUseProgram(program)
+
+        # Build buffer
+
+        # Request a buffer slot from GPU
+        buf = gl.glGenBuffers(1)
+
+        # Make this buffer the default one
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buf)
+
+        # Upload data
+        gl.glBufferData(gl.GL_ARRAY_BUFFER, self.data.nbytes, self.data,
+                        gl.GL_DYNAMIC_DRAW)
+
+        # Bind attributes
+        stride = self.data.strides[0]
+        offset = ctypes.c_void_p(0)
+        loc = gl.glGetAttribLocation(program, "position")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buf)
+        gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, stride, offset)
+
+        offset = ctypes.c_void_p(self.data.dtype["position"].itemsize)
+        loc = gl.glGetAttribLocation(program, "color")
+        gl.glEnableVertexAttribArray(loc)
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buf)
+        gl.glVertexAttribPointer(loc, 4, gl.GL_FLOAT, False, stride, offset)
+
+        # Bind uniforms
+        # --------------------------------------
+        loc = gl.glGetUniformLocation(program, "scale")
+        gl.glUniform1f(loc, 1.0)
+
+    def on_draw(self, event):
+        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+        gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
+
+    def on_resize(self, event):
+        gl.glViewport(0, 0, *event.size)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/colored_cube.py b/examples/tutorial/gloo/colored_cube.py
new file mode 100644
index 0000000..7ebe8a3
--- /dev/null
+++ b/examples/tutorial/gloo/colored_cube.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+
+import numpy as np
+
+from vispy import app, gloo
+from vispy.gloo import Program, VertexBuffer, IndexBuffer
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.geometry import create_cube
+
+
+vertex = """
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+attribute vec3 position;
+attribute vec4 color;
+varying vec4 v_color;
+void main()
+{
+    v_color = color;
+    gl_Position = projection * view * model * vec4(position,1.0);
+}
+"""
+
+fragment = """
+varying vec4 v_color;
+void main()
+{
+    gl_FragColor = v_color;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Colored cube',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build cube data
+        V, I, _ = create_cube()
+        vertices = VertexBuffer(V)
+        self.indices = IndexBuffer(I)
+
+        # Build program
+        self.program = Program(vertex, fragment)
+        self.program.bind(vertices)
+
+        # Build view, model, projection & normal
+        view = np.eye(4, dtype=np.float32)
+        model = np.eye(4, dtype=np.float32)
+        translate(view, 0, 0, -5)
+        self.program['model'] = model
+        self.program['view'] = view
+        self.phi, self.theta = 0, 0
+        gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True)
+        self.timer.start()
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+        self.program.draw('triangles', self.indices)
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+        projection = perspective(45.0, event.size[0] / float(event.size[1]),
+                                 2.0, 10.0)
+        self.program['projection'] = projection
+
+    def on_timer(self, event):
+        self.theta += .5
+        self.phi += .5
+        model = np.eye(4, dtype=np.float32)
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        self.program['model'] = model
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/colored_quad.py b/examples/tutorial/gloo/colored_quad.py
new file mode 100644
index 0000000..5abbeca
--- /dev/null
+++ b/examples/tutorial/gloo/colored_quad.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+
+from vispy import app, gloo
+from vispy.gloo import Program
+
+vertex = """
+    attribute vec4 color;
+    attribute vec2 position;
+    varying vec4 v_color;
+    void main()
+    {
+        gl_Position = vec4(position, 0.0, 1.0);
+        v_color = color;
+    } """
+
+fragment = """
+    varying vec4 v_color;
+    void main()
+    {
+        gl_FragColor = v_color;
+    } """
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Colored quad',
+                            keys='interactive')
+
+    def on_initialize(self, event):
+        # Build program & data
+        self.program = Program(vertex, fragment, count=4)
+        self.program['color'] = [(1, 0, 0, 1), (0, 1, 0, 1),
+                                 (0, 0, 1, 1), (1, 1, 0, 1)]
+        self.program['position'] = [(-1, -1), (-1, +1),
+                                    (+1, -1), (+1, +1)]
+
+    def on_draw(self, event):
+        gloo.clear(color='white')
+        self.program.draw('triangle_strip')
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/lighted_cube.py b/examples/tutorial/gloo/lighted_cube.py
new file mode 100644
index 0000000..2967f06
--- /dev/null
+++ b/examples/tutorial/gloo/lighted_cube.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+
+import numpy as np
+
+from vispy import gloo, app
+from vispy.gloo import Program, VertexBuffer, IndexBuffer
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.geometry import create_cube
+
+
+vertex = """
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform vec4 u_color;
+
+attribute vec3 position;
+attribute vec3 normal;
+attribute vec4 color;
+
+varying vec3 v_position;
+varying vec3 v_normal;
+varying vec4 v_color;
+
+void main()
+{
+    v_normal = normal;
+    v_position = position;
+    v_color = color * u_color;
+    gl_Position = u_projection * u_view * u_model * vec4(position,1.0);
+}
+"""
+
+fragment = """
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_normal;
+
+uniform vec3 u_light_intensity;
+uniform vec3 u_light_position;
+
+varying vec3 v_position;
+varying vec3 v_normal;
+varying vec4 v_color;
+
+void main()
+{
+    // Calculate normal in world coordinates
+    vec3 normal = normalize(u_normal * vec4(v_normal,1.0)).xyz;
+
+    // Calculate the location of this fragment (pixel) in world coordinates
+    vec3 position = vec3(u_view*u_model * vec4(v_position, 1));
+
+    // Calculate the vector from this pixels surface to the light source
+    vec3 surfaceToLight = u_light_position - position;
+
+    // Calculate the cosine of the angle of incidence (brightness)
+    float brightness = dot(normal, surfaceToLight) /
+                      (length(surfaceToLight) * length(normal));
+    brightness = max(min(brightness,1.0),0.0);
+
+    // Calculate final color of the pixel, based on:
+    // 1. The angle of incidence: brightness
+    // 2. The color/intensities of the light: light.intensities
+    // 3. The texture and texture coord: texture(tex, fragTexCoord)
+
+    gl_FragColor = v_color * brightness * vec4(u_light_intensity, 1);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Lighted cube',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build cube data
+        V, F, O = create_cube()
+        vertices = VertexBuffer(V)
+        self.faces = IndexBuffer(F)
+        self.outline = IndexBuffer(O)
+
+        # Build view, model, projection & normal
+        # --------------------------------------
+        self.view = np.eye(4, dtype=np.float32)
+        model = np.eye(4, dtype=np.float32)
+        translate(self.view, 0, 0, -5)
+        normal = np.array(np.matrix(np.dot(self.view, model)).I.T)
+
+        # Build program
+        # --------------------------------------
+        self.program = Program(vertex, fragment)
+        self.program.bind(vertices)
+        self.program["u_light_position"] = 2, 2, 2
+        self.program["u_light_intensity"] = 1, 1, 1
+        self.program["u_model"] = model
+        self.program["u_view"] = self.view
+        self.program["u_normal"] = normal
+        self.phi, self.theta = 0, 0
+
+        # OpenGL initalization
+        # --------------------------------------
+        gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True,
+                       polygon_offset=(1, 1),
+                       blend_func=('src_alpha', 'one_minus_src_alpha'),
+                       line_width=0.75)
+        self.timer.start()
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+        # program.draw(gl.GL_TRIANGLES, indices)
+
+        # Filled cube
+        gloo.set_state(blend=False, depth_test=True, polygon_offset_fill=True)
+        self.program['u_color'] = 1, 1, 1, 1
+        self.program.draw('triangles', self.faces)
+
+        # Outlined cube
+        gloo.set_state(polygon_offset_fill=False, blend=True, depth_mask=False)
+        self.program['u_color'] = 0, 0, 0, 1
+        self.program.draw('lines', self.outline)
+        gloo.set_state(depth_mask=True)
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+        projection = perspective(45.0, event.size[0] / float(event.size[1]),
+                                 2.0, 10.0)
+        self.program['u_projection'] = projection
+
+    def on_timer(self, event):
+        self.theta += .5
+        self.phi += .5
+        model = np.eye(4, dtype=np.float32)
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        normal = np.array(np.matrix(np.dot(self.view, model)).I.T)
+        self.program['u_model'] = model
+        self.program['u_normal'] = normal
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/outlined_cube.py b/examples/tutorial/gloo/outlined_cube.py
new file mode 100644
index 0000000..97b2903
--- /dev/null
+++ b/examples/tutorial/gloo/outlined_cube.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+
+import numpy as np
+
+from vispy import gloo, app
+from vispy.gloo import Program, VertexBuffer, IndexBuffer
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.geometry import create_cube
+
+vertex = """
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform vec4 u_color;
+attribute vec3 position;
+attribute vec4 color;
+varying vec4 v_color;
+void main()
+{
+    v_color = u_color * color;
+    gl_Position = u_projection * u_view * u_model * vec4(position,1.0);
+}
+"""
+
+fragment = """
+varying vec4 v_color;
+void main()
+{
+    gl_FragColor = v_color;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Rotating cube',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build cube data
+        V, I, O = create_cube()
+        vertices = VertexBuffer(V)
+        self.faces = IndexBuffer(I)
+        self.outline = IndexBuffer(O)
+
+        # Build program
+        # --------------------------------------
+        self.program = Program(vertex, fragment)
+        self.program.bind(vertices)
+
+        # Build view, model, projection & normal
+        # --------------------------------------
+        view = np.eye(4, dtype=np.float32)
+        model = np.eye(4, dtype=np.float32)
+        translate(view, 0, 0, -5)
+        self.program['u_model'] = model
+        self.program['u_view'] = view
+        self.phi, self.theta = 0, 0
+
+        # OpenGL initalization
+        # --------------------------------------
+        gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True,
+                       polygon_offset=(1, 1), line_width=0.75,
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+        self.timer.start()
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+
+        # Filled cube
+        gloo.set_state(blend=False, depth_test=True, polygon_offset_fill=True)
+        self.program['u_color'] = 1, 1, 1, 1
+        self.program.draw('triangles', self.faces)
+
+        # Outlined cube
+        gloo.set_state(blend=True, depth_mask=False, polygon_offset_fill=False)
+        self.program['u_color'] = 0, 0, 0, 1
+        self.program.draw('lines', self.outline)
+        gloo.set_state(depth_mask=True)
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+        projection = perspective(45.0, event.size[0] / float(event.size[1]),
+                                 2.0, 10.0)
+        self.program['u_projection'] = projection
+
+    def on_timer(self, event):
+        self.theta += .5
+        self.phi += .5
+        model = np.eye(4, dtype=np.float32)
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        self.program['u_model'] = model
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/rotating_quad.py b/examples/tutorial/gloo/rotating_quad.py
new file mode 100644
index 0000000..68a771b
--- /dev/null
+++ b/examples/tutorial/gloo/rotating_quad.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+
+from vispy import gloo, app
+from vispy.gloo import Program
+
+vertex = """
+    uniform float theta;
+    attribute vec4 color;
+    attribute vec2 position;
+    varying vec4 v_color;
+    void main()
+    {
+        float ct = cos(theta);
+        float st = sin(theta);
+        float x = 0.75* (position.x*ct - position.y*st);
+        float y = 0.75* (position.x*st + position.y*ct);
+        gl_Position = vec4(x, y, 0.0, 1.0);
+        v_color = color;
+    } """
+
+fragment = """
+    varying vec4 v_color;
+    void main()
+    {
+        gl_FragColor = v_color;
+    } """
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Rotating quad',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build program & data
+        self.program = Program(vertex, fragment, count=4)
+        self.program['color'] = [(1, 0, 0, 1), (0, 1, 0, 1),
+                                 (0, 0, 1, 1), (1, 1, 0, 1)]
+        self.program['position'] = [(-1, -1), (-1, +1),
+                                    (+1, -1), (+1, +1)]
+        self.clock = 0
+        self.timer.start()
+
+    def on_draw(self, event):
+        gloo.set_clear_color('white')
+        gloo.clear(color=True)
+        self.program.draw('triangle_strip')
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+
+    def on_timer(self, event):
+        self.clock += 0.001 * 1000.0 / 60.
+        self.program['theta'] = self.clock
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/textured_cube.py b/examples/tutorial/gloo/textured_cube.py
new file mode 100644
index 0000000..b5c74f8
--- /dev/null
+++ b/examples/tutorial/gloo/textured_cube.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+import numpy as np
+
+from vispy import gloo, app
+from vispy.gloo import Program, VertexBuffer, IndexBuffer
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.geometry import create_cube
+
+
+vertex = """
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+uniform sampler2D texture;
+
+attribute vec3 position;
+attribute vec2 texcoord;
+
+varying vec2 v_texcoord;
+void main()
+{
+    gl_Position = projection * view * model * vec4(position,1.0);
+    v_texcoord = texcoord;
+}
+"""
+
+fragment = """
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+void main()
+{
+    gl_FragColor = texture2D(texture, v_texcoord);
+}
+"""
+
+
+def checkerboard(grid_num=8, grid_size=32):
+    row_even = grid_num / 2 * [0, 1]
+    row_odd = grid_num / 2 * [1, 0]
+    Z = np.row_stack(grid_num / 2 * (row_even, row_odd)).astype(np.uint8)
+    return 255 * Z.repeat(grid_size, axis=0).repeat(grid_size, axis=1)
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Textured cube',
+                            keys='interactive')
+        self.timer = app.Timer('auto', self.on_timer)
+
+    def on_initialize(self, event):
+        # Build cube data
+        V, I, _ = create_cube()
+        vertices = VertexBuffer(V)
+        self.indices = IndexBuffer(I)
+
+        # Build program
+        self.program = Program(vertex, fragment)
+        self.program.bind(vertices)
+
+        # Build view, model, projection & normal
+        view = np.eye(4, dtype=np.float32)
+        model = np.eye(4, dtype=np.float32)
+        translate(view, 0, 0, -5)
+        self.program['model'] = model
+        self.program['view'] = view
+        self.program['texture'] = checkerboard()
+
+        self.phi, self.theta = 0, 0
+
+        # OpenGL initalization
+        gloo.set_state(clear_color=(0.30, 0.30, 0.35, 1.00), depth_test=True)
+        self.timer.start()
+
+    def on_draw(self, event):
+        gloo.clear(color=True, depth=True)
+        self.program.draw('triangles', self.indices)
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+        projection = perspective(45.0, event.size[0] / float(event.size[1]),
+                                 2.0, 10.0)
+        self.program['projection'] = projection
+
+    def on_timer(self, event):
+        self.theta += .5
+        self.phi += .5
+        model = np.eye(4, dtype=np.float32)
+        rotate(model, self.theta, 0, 0, 1)
+        rotate(model, self.phi, 0, 1, 0)
+        self.program['model'] = model
+        self.update()
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/tutorial/gloo/textured_quad.py b/examples/tutorial/gloo/textured_quad.py
new file mode 100644
index 0000000..d60a908
--- /dev/null
+++ b/examples/tutorial/gloo/textured_quad.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
+# -----------------------------------------------------------------------------
+
+import numpy as np
+
+from vispy import gloo, app
+from vispy.gloo import Program
+
+vertex = """
+    attribute vec2 position;
+    attribute vec2 texcoord;
+    varying vec2 v_texcoord;
+    void main()
+    {
+        gl_Position = vec4(position, 0.0, 1.0);
+        v_texcoord = texcoord;
+    } """
+
+fragment = """
+    uniform sampler2D texture;
+    varying vec2 v_texcoord;
+    void main()
+    {
+        gl_FragColor = texture2D(texture, v_texcoord);
+    } """
+
+
+def checkerboard(grid_num=8, grid_size=32):
+    row_even = grid_num / 2 * [0, 1]
+    row_odd = grid_num / 2 * [1, 0]
+    Z = np.row_stack(grid_num / 2 * (row_even, row_odd)).astype(np.uint8)
+    return 255 * Z.repeat(grid_size, axis=0).repeat(grid_size, axis=1)
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, size=(512, 512), title='Textured quad',
+                            keys='interactive')
+
+    def on_initialize(self, event):
+        # Build program & data
+        self.program = Program(vertex, fragment, count=4)
+        self.program['position'] = [(-1, -1), (-1, +1),
+                                    (+1, -1), (+1, +1)]
+        self.program['texcoord'] = [(0, 0), (1, 0), (0, 1), (1, 1)]
+        self.program['texture'] = checkerboard()
+
+    def on_draw(self, event):
+        gloo.set_clear_color('white')
+        gloo.clear(color=True)
+        self.program.draw('triangle_strip')
+
+    def on_resize(self, event):
+        gloo.set_viewport(0, 0, *event.size)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/make/__init__.py b/make/__init__.py
index 8209ac1..41c1a9d 100644
--- a/make/__init__.py
+++ b/make/__init__.py
@@ -3,4 +3,4 @@ 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
+from .make import Maker  # noqa
diff --git a/make/__main__.py b/make/__main__.py
index 9690a45..bd08f27 100644
--- a/make/__main__.py
+++ b/make/__main__.py
@@ -10,4 +10,4 @@ START_DIR = os.path.abspath(os.getcwd())
 try:
     Maker(sys.argv)
 finally:
-    os.chdir(START_DIR)
\ No newline at end of file
+    os.chdir(START_DIR)
diff --git a/make/make.py b/make/make.py
index 875c20e..d740af0 100644
--- a/make/make.py
+++ b/make/make.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for mo
 
 """
@@ -10,48 +10,54 @@ Convenience tools for vispy developers
 
 """
 
-from __future__ import print_function, division
+from __future__ import division, print_function
 
 import sys
 import os
+from os import path as op
+import time
 import shutil
 import subprocess
 import re
+import webbrowser
+import traceback
+import numpy as np
 
 # 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__))
+START_DIR = op.abspath(os.getcwd())
+THIS_DIR = op.abspath(op.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')):
+    ROOT_DIR = op.abspath(op.join(THIS_DIR, subdir))
+    if op.isfile(op.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')
+DOC_DIR = op.join(ROOT_DIR, 'doc')
 #
-WEBSITE_DIR = os.path.join(ROOT_DIR, '_website')
+WEBSITE_DIR = op.join(ROOT_DIR, '_website')
 WEBSITE_REPO = 'git at github.com:vispy/vispy-website'
 #
-PAGES_DIR =  os.path.join(ROOT_DIR, '_gh-pages')
+PAGES_DIR = op.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_DIR = op.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
@@ -66,8 +72,21 @@ class Maker:
             func(arg)
         else:
             sys.exit('Invalid command: "%s"' % command)
-    
-    
+
+    def coverage_html(self, arg):
+        """Generate html report from .coverage and launch"""
+        print('Generating HTML...')
+        from coverage import coverage
+        cov = coverage(auto_data=False, branch=True, data_suffix=None,
+                       source=['vispy'])  # should match testing/_coverage.py
+        cov.load()
+        cov.html_report()
+        print('Done, launching browser.')
+        fname = op.join(os.getcwd(), 'htmlcov', 'index.html')
+        if not op.isfile(fname):
+            raise IOError('Generated file not found: %s' % fname)
+        webbrowser.open_new_tab(fname)
+
     def help(self, arg):
         """ Show help message. Use 'help X' to get more help on command X. """
         if arg:
@@ -79,26 +98,25 @@ class Maker:
                 print()
             else:
                 sys.exit('Cannot show help on unknown command: "%s"' % command)
-            
+
         else:
-            print(__doc__.strip()+ '\n\nCommands:\n')
+            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()
+                preamble = command.ljust(11)  # longest command is 9 or 10
+                # 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
+                * html - build html
+                * show - show the docs in your browser
         """
         # Prepare
-        build_dir = os.path.join(DOC_DIR, '_build')
+        build_dir = op.join(DOC_DIR, '_build')
         if not arg:
             return self.help('doc')
         # Go
@@ -106,32 +124,32 @@ class Maker:
             sphinx_clean(build_dir)
             sphinx_build(DOC_DIR, build_dir)
         elif 'show' == arg:
-            sphinx_show(os.path.join(build_dir, 'html'))
+            sphinx_show(op.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
+                * 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')
-        
+        build_dir = op.join(WEBSITE_DIR, '_build')
+        html_dir = op.join(build_dir, 'html')
+
         # Clone repo for website if needed, make up-to-date otherwise
-        if not os.path.isdir(WEBSITE_DIR):
+        if not op.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')
-        
+
+        if not arg:
+            return self.help('website')
+
         # Go
         if 'html' == arg:
             sphinx_clean(build_dir)
@@ -142,67 +160,108 @@ class Maker:
         elif 'upload' == arg:
             sphinx_upload(PAGES_DIR)
             print()
-            print("Do not forget to also commit+push your changes to '_website'")
+            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()
-    
-    
+        """ Run tests:
+                * full - run all tests
+                * nose - run nose tests (also for each backend)
+                * any backend name (e.g. pyside, pyqt4, glut, sdl2, etc.) -
+                  run tests for the given backend
+                * nobackend - run tests that do not require a backend
+                * extra - run extra tests (line endings and style)
+                * lineendings - test line ending consistency
+                * flake - flake style testing (PEP8 and more)
+        """
+        if not arg:
+            return self.help('test')
+        from vispy import test
+        try:
+            test(*(arg.split()))
+        except Exception as err:
+            print(err)
+            if not isinstance(err, RuntimeError):
+                type_, value, tb = sys.exc_info()
+                traceback.print_exception(type, value, tb)
+            raise SystemExit(1)
+
     def images(self, arg):
         """ Create images (screenshots). Subcommands:
-            * gallery - make screenshots for the gallery
-            * test - make screenshots for testing
-            * upload - upload the images repository
+                * gallery - make screenshots for the gallery
+                * test - make screenshots for testing
+                * upload - upload the gallery images repository
+                * clean - delete existing files
         """
-        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):
+        if not op.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')
-        
+
+        if not arg:
+            return self.help('images')
+
         # Create subdirs if needed
-        for subdir in ['gallery', 'thumbs', 'test']:
-            subdir = os.path.join(IMAGES_DIR, subdir)
-            if not os.path.isdir(subdir):
+        for subdir in ['gallery', 'thumbs', 'carousel', 'test']:
+            subdir = op.join(IMAGES_DIR, subdir)
+            if not op.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')
+            sys.exit('images test command not yet implemented')
         elif arg == 'upload':
             sphinx_upload(IMAGES_DIR)
+        elif arg == 'clean':
+            self._images_delete()
         else:
-            sys.exit('Command "website" does not have subcommand "%s"' % arg)
-    
-    
+            sys.exit('Command "images" does not have subcommand "%s"' % arg)
+
+    def _images_delete(self):
+        examples_dir = op.join(ROOT_DIR, 'examples')
+        gallery_dir = op.join(IMAGES_DIR, 'gallery')
+
+        # Process all files ...
+        print('Deleting existing gallery images')
+        n = 0
+        tot = 0
+        for filename, name in get_example_filenames(examples_dir):
+            tot += 1
+            name = name.replace('/', '__')  # We use flat names
+            imagefilename = op.join(gallery_dir, name + '.png')
+            if op.isfile(imagefilename):
+                n += 1
+                os.remove(imagefilename)
+        print('Removed %s of %s possible files' % (n, tot))
+
     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')
-        
+        from vispy.io import imsave
+        from vispy.gloo.util import _screenshot
+        examples_dir = op.join(ROOT_DIR, 'examples')
+        gallery_dir = op.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')
-            
+            imagefilename = op.join(gallery_dir, name + '.png')
+
+            # Check if we need to take a sceenshot
+            if op.isfile(imagefilename):
+                print('Skip:   %s screenshot already present.' % name)
+                continue
+
             # Check if should make a screenshot
             frames = []
             lines = open(filename, 'rt').read().splitlines()
@@ -214,71 +273,135 @@ class Maker:
                     frames = [int(i) for i in frames.split(':')]
                     if not frames:
                         frames = [0]
-                    if len(frames)>1:
+                    if len(frames) > 1:
                         frames = list(range(*frames))
                     break
             else:
+                print('Ignore: %s, no hint' % name)
                 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)
+            print('Grab:   %s screenshots (%s)' % (name, len(frames)))
+            try:
+                m = imp.load_source('vispy_example_' + name, filename)
+            except Exception as exp:
+                print('*Err*:  %s, got "%s"' % (name, str(exp)))
             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
+                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]))
+                    # Ensure we don't have alpha silliness
+                    im = np.array(im)
+                    im[:, :, 3] = 255
                     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()
-            
+            # Get canvas
+            if hasattr(m, 'canvas'):
+                c = m.canvas  # scene examples
+            elif hasattr(m, 'Canvas'):
+                c = m.Canvas()
+            else:
+                print('Ignore: %s, no canvas' % name)
+            c.events.draw.connect(grabscreenshot)
+            # Show it and draw as many frames as needed
+            with c:
+                n = 0
+                limit = 10000
+                while not m.done and n < limit:
+                    c.update()
+                    c.app.process_events()
+                    n += 1
+                if n >= limit or len(frames) > 0:
+                    raise RuntimeError('Could not collect image for %s' % name)
             # 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)
-    
-    
+                imageio.mimsave(imagefilename[:-3] + '.gif', m.images)
+
     def _images_thumbnails(self):
-        from vispy.util.dataio import imsave, imread
+        from vispy.io 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')
+        gallery_dir = op.join(IMAGES_DIR, 'gallery')
+        thumbs_dir = op.join(IMAGES_DIR, 'thumbs')
+        carousel_dir = op.join(IMAGES_DIR, 'carousel')
         for fname in os.listdir(gallery_dir):
-            filename1 = os.path.join(gallery_dir, fname)
-            filename2 = os.path.join(thumbs_dir, fname)
+            filename1 = op.join(gallery_dir, fname)
+            filename2 = op.join(thumbs_dir, fname)
+            filename3 = op.join(carousel_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)
+            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)
 
+            newy = 160  # This should match the carousel size!
+            newx = int(newy * im.shape[1] / im.shape[0])
+            im = (resize(im, (newy, newx), 1) * 255).astype(np.uint8)
+            imsave(filename3, im)
 
+            print('Created thumbnail and carousel %s' % fname)
 
-## Functions used by the maker
+    def copyright(self, arg):
+        """ Update all copyright notices to the current year.
+        """
+        # Initialize
+        TEMPLATE = "# Copyright (c) %i, Vispy Development Team."
+        CURYEAR = int(time.strftime('%Y'))
+        OLDTEXT = TEMPLATE % (CURYEAR - 1)
+        NEWTEXT = TEMPLATE % CURYEAR
+        # Initialize counts
+        count_ok, count_replaced = 0, 0
+
+        # Processing the whole root directory
+        for dirpath, dirnames, filenames in os.walk(ROOT_DIR):
+            # Check if we should skip this directory
+            reldirpath = op.relpath(dirpath, ROOT_DIR)
+            if reldirpath[0] in '._' or reldirpath.endswith('__pycache__'):
+                continue
+            if op.split(reldirpath)[0] in ('build', 'dist'):
+                continue
+            # Process files
+            for fname in filenames:
+                if not fname.endswith('.py'):
+                    continue
+                # Open and check
+                filename = op.join(dirpath, fname)
+                text = open(filename, 'rt').read()
+                if NEWTEXT in text:
+                    count_ok += 1
+                elif OLDTEXT in text:
+                    text = text.replace(OLDTEXT, NEWTEXT)
+                    open(filename, 'wt').write(text)
+                    print(
+                        '  Update copyright year in %s/%s' %
+                        (reldirpath, fname))
+                    count_replaced += 1
+                elif 'copyright' in text[:200].lower():
+                    print(
+                        '  Unknown copyright mentioned in %s/%s' %
+                        (reldirpath, fname))
+        # Report
+        print('Replaced %i copyright statements' % count_replaced)
+        print('Found %i copyright statements up to date' % count_ok)
+
+
+# Functions used by the maker
 
 if sys.version_info[0] < 3:
-    input = raw_input
+    input = raw_input  # noqa
 
 
 def sh(cmd):
@@ -299,26 +422,28 @@ def sh2(cmd):
 
 
 def sphinx_clean(build_dir):
-        if os.path.isdir(build_dir):
-            shutil.rmtree(build_dir)
-        os.mkdir(build_dir)
-        print('Cleared build directory.')
+    if op.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
-                ))
+    ret = sphinx.main(('sphinx-build',  # Dummy
+                       '-b', 'html',
+                       '-d', op.join(build_dir, 'doctrees'),
+                       src_dir,  # Source
+                       op.join(build_dir, 'html'),  # Dest
+                       ))
+    if ret != 0:
+        raise RuntimeError('Sphinx error: %s' % ret)
     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):
+    index_html = op.join(html_dir, 'index.html')
+    if not op.isfile(index_html):
         sys.exit('Cannot show pages, build the html first.')
     import webbrowser
     webbrowser.open_new_tab(index_html)
@@ -326,28 +451,29 @@ def sphinx_show(html_dir):
 
 def sphinx_copy_pages(html_dir, pages_dir, pages_repo):
     # Create the pages repo if needed
-    if not os.path.isdir(pages_dir):
+    if not op.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')
+    os.chdir('..')
     # 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)
+    tmp_git_dir = op.join(ROOT_DIR, pages_dir + '_git')
+    shutil.move(op.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'))
+        shutil.move(tmp_git_dir, op.join(pages_dir, '.git'))
     finally:
-        if os.path.isdir(tmp_git_dir):
+        if op.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))
+        shutil.copyfile(op.join(WEBSITE_DIR, fname),
+                        op.join(pages_dir, fname))
     # Messages
     os.chdir(pages_dir)
     sh('git status')
@@ -361,10 +487,10 @@ 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)
+    branch = re.match('On branch (.*)$', status).group(1)
     if branch != 'master':
         e = 'On %r, git branch is %r, MUST be "master"' % (repo_dir,
-                                                            branch)
+                                                           branch)
         raise RuntimeError(e)
     # Show repo and ask confirmation
     print()
@@ -386,13 +512,14 @@ def sphinx_upload(repo_dir):
 
 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 
+    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)
+        for fname in sorted(filenames):
+            if not fname.endswith('.py'):
+                continue
+            filename = op.join(dirpath, fname)
             name = filename[len(example_dir):].lstrip('/\\')[:-3]
             name = name.replace('\\', '/')
             yield filename, name
@@ -400,7 +527,6 @@ def get_example_filenames(example_dir):
 
 if __name__ == '__main__':
     try:
-        Maker(sys.argv)
-        # Maker(('bla', 'gallery'))
+        m = Maker(sys.argv)
     finally:
         os.chdir(START_DIR)
diff --git a/nosetests.py b/nosetests.py
deleted file mode 100644
index aabfe68..0000000
--- a/nosetests.py
+++ /dev/null
@@ -1,6 +0,0 @@
-""" 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
index 410d99e..c3f310a 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ Preparations:
 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 
+  * python setup.py sdist upload -r http://testpypi.python.org/pypi
   * pip install -i http://testpypi.python.org/pypi
 
 Define the version:
@@ -29,10 +29,17 @@ Generate and upload package (preferably on Windows)
 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 os import path as op
+
+try:
+    # use setuptools namespace, allows for "develop"
+    import setuptools  # noqa, analysis:ignore
+except ImportError:
+    pass  # it's not essential for installation
 from distutils.core import setup
 
 name = 'vispy'
@@ -42,10 +49,10 @@ description = 'Interactive visualization in Python'
 # Get version and docstring
 __version__ = None
 __doc__ = ''
-docStatus = 0 # Not started, in progress, done
+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__')):
+    if (line.startswith('version_info') or line.startswith('__version__')):
         exec(line.strip())
     elif line.startswith('"""'):
         if docStatus == 0:
@@ -57,49 +64,51 @@ for line in open(initFile).readlines():
         __doc__ += line
 
 
+def package_tree(pkgroot):
+    path = os.path.dirname(__file__)
+    subdirs = [os.path.relpath(i[0], path).replace(os.path.sep, '.')
+               for i in os.walk(os.path.join(path, pkgroot))
+               if '__init__.py' in i[2]]
+    return subdirs
+
+
 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,
-    
+    name=name,
+    version=__version__,
+    author='Vispy contributors',
+    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'],
+    packages=package_tree('vispy'),
+    package_dir={
+        'vispy': 'vispy'},
+    package_data={
+        'vispy': [op.join('io', '_data', '*'),
+                  op.join('html', 'static', 'js', '*'),
+                  op.join('app', 'tests', 'qt-designer.ui')]},
+    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',
-          ],
-    )
+        '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/_link_vispy.py b/vispy.proxy.py
similarity index 91%
rename from _link_vispy.py
rename to vispy.proxy.py
index 7094098..5f776cd 100644
--- a/_link_vispy.py
+++ b/vispy.proxy.py
@@ -40,7 +40,12 @@ _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)
+try:
+  __import__(MODULE_NAME, level=0)
+except Exception as err:
+  sys.modules[MODULE_NAME] = _old  # Prevent KeyError
+  raise
+
 
 # Clean up after ourselves
 if PARENT_DIR_OF_MODULE in sys.path:
diff --git a/vispy/__init__.py b/vispy/__init__.py
index 9478f56..a76c180 100644
--- a/vispy/__init__.py
+++ b/vispy/__init__.py
@@ -1,104 +1,34 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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
+=====
+Vispy
+=====
 
-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.
+Vispy is a **high-performance interactive 2D/3D data visualization
+library**. Vispy leverages the computational power of modern **Graphics
+Processing Units (GPUs)** through the **OpenGL** library to display very
+large datasets.
 
-For more information see http://vispy.org.
+For more information, see http://vispy.org.
 
 """
 
-from __future__ import print_function, division, absolute_import
+from __future__ import division
 
-# Definition of the version number
-__version__ = '0.2.1'
-
-
-from vispy.util.event import EmitterGroup, EventEmitter, Event
-from vispy.util import keys, dataio
+__all__ = ['use', 'sys_info', 'set_log_level', 'test']
 
+# Definition of the version number
+version_info = 0, 3, 0, ''  # major, minor, patch, extra
 
-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,
-)
+# Nice string for the version (mimic how IPython composes its version str)
+__version__ = '-'.join(map(str, version_info)).replace('-', '.', 2).strip('-')
 
+from .util import (_parse_command_line_arguments, config,  # noqa
+                   set_log_level, keys, sys_info, test)  # noqa
+from .util.wrappers import use  # noqa
 
-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()
+_parse_command_line_arguments()
diff --git a/vispy/app/__init__.py b/vispy/app/__init__.py
index 29a949f..6762f81 100644
--- a/vispy/app/__init__.py
+++ b/vispy/app/__init__.py
@@ -1,65 +1,18 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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. 
+"""
+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()
-
+from __future__ import division
 
-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()
+from .application import Application  # noqa
+from ._default_app import use_app, create, run, quit, process_events  # noqa
+from .canvas import Canvas, MouseEvent, KeyEvent  # noqa
+from .timer import Timer  # noqa
+from . import base  # noqa
+from ._config import get_default_config  # noqa
diff --git a/vispy/app/_config.py b/vispy/app/_config.py
new file mode 100644
index 0000000..430faa0
--- /dev/null
+++ b/vispy/app/_config.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from copy import deepcopy
+
+_default_dict = dict(red_size=8, green_size=8, blue_size=8, alpha_size=8,
+                     depth_size=16, stencil_size=0, double_buffer=True,
+                     stereo=False, samples=0)
+
+
+def get_default_config():
+    """Get the default OpenGL context configuration
+
+    Returns
+    -------
+    config : dict
+        Dictionary of config values.
+    """
+    return deepcopy(_default_dict)
diff --git a/vispy/app/_default_app.py b/vispy/app/_default_app.py
new file mode 100644
index 0000000..dfc5581
--- /dev/null
+++ b/vispy/app/_default_app.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from .application import Application
+
+# Initialize default app
+# Only for use within *this* module. 
+# One should always call use_app() to obtain the default app.
+default_app = None
+
+
+def use_app(backend_name=None):
+    """ Get/create the default Application object
+    
+    It is safe to call this function multiple times, as long as
+    backend_name is None or matches the already selected backend.
+    
+    Parameters
+    ----------
+    backend_name : str | None
+        The name of the backend application to use. If not specified, Vispy
+        tries to select a backend automatically. See ``vispy.use()`` for
+        details.
+    
+    """
+    global default_app
+
+    # If we already have a default_app, raise error or return
+    if default_app is not None:
+        names = default_app.backend_name.lower().replace('(', ' ').strip(') ')
+        names = [name for name in names.split(' ') if name]
+        if backend_name and backend_name.lower() not in names:
+            raise RuntimeError('Can only select a backend once.')
+        else:
+            return default_app  # Current backend matches backend_name
+
+    # Create default app
+    default_app = Application(backend_name)
+    return default_app
+
+
+def create():
+    """Create the native application.
+    """
+    use_app()
+    return default_app.create()
+
+
+def run():
+    """Enter the native GUI event loop.
+    """
+    use_app()
+    return default_app.run()
+
+
+def quit():
+    """Quit the native GUI event loop.
+    """
+    use_app()
+    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.
+    """
+    use_app()
+    return default_app.process_events()
diff --git a/vispy/app/application.py b/vispy/app/application.py
index 5c658b9..ff8616e 100644
--- a/vispy/app/application.py
+++ b/vispy/app/application.py
@@ -1,40 +1,51 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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
+from __future__ import division
 
+import os
 import sys
 
-import vispy
-from vispy.app.backends import BACKENDS, BACKENDMAP, ATTEMPTED_BACKENDS
-
+from . import backends
+from .backends import CORE_BACKENDS, BACKEND_NAMES, BACKENDMAP, TRIED_BACKENDS
+from .. import config
+from .base import BaseApplicationBackend as ApplicationBackend  # noqa
+from ..util import logger
 
 
 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.
+    """Representation of the vispy application
+
+    This wraps a native GUI application instance. Vispy has a default
+    instance of this class that can be created/obtained via 
+    `vispy.app.use_app()`.
+
+    Parameters
+    ----------
+    backend_name : str | None
+        The name of the backend application to use. If not specified,
+        Vispy tries to select a backend automatically. See ``vispy.use()``
+        for details.
+    
+    Notes
+    -----
+    Upon creating an Application object, a backend is selected, but the
+    native backend application object is only created when `create()`
+    is called or `native` is used. The Canvas and Timer do this
+    automatically.
     
     """
-    
-    def __init__(self):
+
+    def __init__(self, backend_name=None):
         self._backend_module = None
         self._backend = None
+        self._use(backend_name)
     
     def __repr__(self):
         name = self.backend_name
@@ -42,7 +53,7 @@ class Application(object):
             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.
@@ -51,154 +62,120 @@ class Application(object):
             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.
         """
+        # Ensure that the native app exists
         self.native
-    
+
     def run(self):
-        """ Enter the native GUI event loop. 
+        """ 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.
-        
+    def _use(self, backend_name=None):
+        """Select a backend by name. See class docstring for details.
         """
-        import vispy.app
-        
+        # See if we're in a specific testing mode
+        test_name = os.getenv('_VISPY_TESTING_TYPE', None)
+        if test_name not in BACKENDMAP:
+            test_name = None
+
+        # Check whether the given name is valid
+        if backend_name is not None:
+            if backend_name.lower() == 'default':
+                backend_name = None  # Explicitly use default, avoid using test
+            elif backend_name.lower() not in BACKENDMAP:
+                raise ValueError('backend_name must be one of %s or None, not '
+                                 '%r' % (BACKEND_NAMES, backend_name))
+        elif test_name is not None:
+            backend_name = test_name.lower()
+            assert backend_name in BACKENDMAP
+
         # 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 ...
+        imported_toolkits = []  # Backends for which the native lib is imported
         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)
+            # We should never hit this, since we check above
+            assert backend_name.lower() in BACKENDMAP.keys()
             # 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:
+            for name, module_name, native_module_name in CORE_BACKENDS:
                 if native_module_name and native_module_name in sys.modules:
+                    imported_toolkits.append(name.lower())
                     backends_to_try.append(name.lower())
             # See if a default is given
-            default_backend = vispy.config['default_backend'].lower()
+            default_backend = 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:
+            for name, module_name, native_module_name in CORE_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))
+            name, module_name, native_module_name = BACKENDMAP[key]
+            TRIED_BACKENDS.append(name)
+            mod_name = 'backends.' + module_name
+            __import__(mod_name, globals(), level=1)
+            mod = getattr(backends, module_name)
+            if not mod.available:
+                msg = ('Could not import backend "%s":\n%s'
+                       % (name, str(mod.why_not)))
                 if not try_others:
+                    # Fail if user wanted to use a specific backend
                     raise RuntimeError(msg)
+                elif key in imported_toolkits:
+                    # Warn if were unable to use an already imported toolkit
+                    msg = ('Although %s is already imported, the %s backend '
+                           'could not\nbe used ("%s"). \nNote that running '
+                           'multiple GUI toolkits simultaneously can cause '
+                           'side effects.' % 
+                           (native_module_name, name, str(mod.why_not))) 
+                    logger.warning(msg)
                 else:
-                    print(msg)
+                    # Inform otherwise
+                    logger.info(msg)
             else:
                 # Success!
-                self._backend_module = getattr(vispy.app.backends, module_name)
+                self._backend_module = mod
+                logger.debug('Selected backend %s' % 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
 
+        # Store classes for app backend and canvas backend
+        self._backend = self.backend_module.ApplicationBackend()
diff --git a/vispy/app/backends/__init__.py b/vispy/app/backends/__init__.py
index 33c7c71..2a47b6a 100644
--- a/vispy/app/backends/__init__.py
+++ b/vispy/app/backends/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
 """ vispy.app.backends
@@ -12,17 +12,37 @@ 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'),
-            ]
+CORE_BACKENDS = [
+    ('PyQt4', '_pyqt4', 'PyQt4'),
+    ('PySide', '_pyside', 'PySide'),
+    ('Pyglet', '_pyglet', 'pyglet'),
+    ('Glfw', '_glfw', 'vispy.ext.glfw'),
+    ('SDL2', '_sdl2', 'sdl2'),
+    ('wx', '_wx', 'wx'),
+    ('EGL', '_egl', 'vispy.ext.egl'),
+    ('Glut', '_glut', 'OpenGL.GLUT'),
+]
+
+# Whereas core backends really represents libraries that can create a
+# canvas, the pseudo backends act more like a proxy.
+PSEUDO_BACKENDS = [
+    ('ipynb_vnc', '_ipynb_vnc', None),
+    ('ipynb_static', '_ipynb_static', None),
+    ('_test', '_test', 'vispy.app.backends._test'),  # add one that will fail
+]
+
+# Combine
+BACKENDS = CORE_BACKENDS + PSEUDO_BACKENDS
+
+# Get list of backend names
+BACKEND_NAMES = [b[0].lower() for b in BACKENDS]
 
 # 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 = []
+# List of attempted backends. For logging.
+TRIED_BACKENDS = []
+
+# Flag for _pyside, _pyqt4 and _qt modules to communicate.
+qt_lib = None
diff --git a/vispy/app/backends/_egl.py b/vispy/app/backends/_egl.py
new file mode 100644
index 0000000..78179f3
--- /dev/null
+++ b/vispy/app/backends/_egl.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+vispy headless backend for egl.
+"""
+
+from __future__ import division
+
+import atexit
+from time import sleep
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util.ptime import time
+
+# -------------------------------------------------------------------- init ---
+
+try:
+    # Inspired by http://www.mesa3d.org/egl.html
+    # This is likely necessary on Linux since proprietary drivers
+    # (e.g., NVIDIA) are unlikely to provide EGL support for now.
+    # XXX TODO: Add use_gl('es2') and somehow incorporate here.
+    # Also would be good to have us_gl('es3'), since libGLESv2.so on linux
+    # seems to support both.
+    from os import environ
+    environ['EGL_SOFTWARE'] = 'true'
+    from ...ext import egl
+    _EGL_DISPLAY = egl.eglGetDisplay()
+    egl.eglInitialize(_EGL_DISPLAY)
+    version = [egl.eglQueryString(_EGL_DISPLAY, x) for x in
+               [egl.EGL_VERSION, egl.EGL_VENDOR, egl.EGL_CLIENT_APIS]]
+    version = version[0] + ' ' + version[1] + ': ' + version[2].strip()
+    atexit.register(egl.eglTerminate, _EGL_DISPLAY)
+except Exception as exp:
+    available, testable, why_not, which = False, False, str(exp), None
+else:
+    # XXX restore "testable" and "available" once it works properly, and
+    # remove from ignore list in .coveragerc
+    available, testable, why_not = False, False, 'Not ready for testing'
+    which = 'EGL ' + str(version)
+
+
+_VP_EGL_ALL_WINDOWS = []
+
+
+def _get_egl_windows():
+    wins = list()
+    for win in _VP_EGL_ALL_WINDOWS:
+        if isinstance(win, CanvasBackend):
+            wins.append(win)
+    return wins
+
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=False,
+    resizable=True,
+    decorate=False,
+    fullscreen=False,
+    context=False,
+    multi_window=True,
+    scroll=False,
+    parent=False,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+
+class SharedContext(BaseSharedContext):
+    _backend = 'egl'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._timers = list()
+
+    def _add_timer(self, timer):
+        if timer not in self._timers:
+            self._timers.append(timer)
+
+    def _vispy_get_backend_name(self):
+        return 'egl'
+
+    def _vispy_process_events(self):
+        for timer in self._timers:
+            timer._tick()
+        wins = _get_egl_windows()
+        for win in wins:
+            if win._needs_draw:
+                win._needs_draw = False
+                win._on_draw()
+
+    def _vispy_run(self):
+        wins = _get_egl_windows()
+        while all(w._surface is not None for w in wins):
+            self._vispy_process_events()
+        self._vispy_quit()  # to clean up
+
+    def _vispy_quit(self):
+        # Close windows
+        wins = _get_egl_windows()
+        for win in wins:
+            win._vispy_close()
+        # tear down timers
+        for timer in self._timers:
+            timer._vispy_stop()
+        self._timers = []
+
+    def _vispy_get_native_app(self):
+        return egl
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(BaseCanvasBackend):
+
+    """ EGL backend for Canvas abstract class."""
+
+    def __init__(self, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        # Create "window"
+        if isinstance(context, dict):
+            self._config = egl.eglChooseConfig(_EGL_DISPLAY)[0]
+            self._context = egl.eglCreateContext(_EGL_DISPLAY, self._config,
+                                                 None)
+        else:
+            self._config, self._context = context.value
+
+        self._surface = None
+        self._vispy_set_size(*size)
+        _VP_EGL_ALL_WINDOWS.append(self)
+        self._initialized = False
+        self._vispy_set_current()
+        self._vispy_canvas = vispy_canvas
+
+    def _destroy_surface(self):
+        if self._surface is not None:
+            egl.eglDestroySurface(_EGL_DISPLAY, self._surface)
+            self._surface = None
+
+    def _vispy_set_size(self, w, h):
+        if self._surface is not None:
+            self._destroy_surface()
+        attrib_list = (egl.EGL_WIDTH, w, egl.EGL_HEIGHT, h)
+        self._surface = egl.eglCreatePbufferSurface(_EGL_DISPLAY, self._config,
+                                                    attrib_list)
+        if self._surface == egl.EGL_NO_SURFACE:
+            raise RuntimeError('Could not create rendering surface')
+        self._size = (w, h)
+        self._vispy_update()
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext((self._config, self._context))
+
+    ####################################
+    # Deal with events we get from vispy
+    @property
+    def _vispy_canvas(self):
+        """ The parent canvas/window """
+        return self._vispy_canvas_
+
+    @_vispy_canvas.setter
+    def _vispy_canvas(self, vc):
+        # Init events when the property is set by Canvas
+        self._vispy_canvas_ = vc
+        if vc is not None and not self._initialized:
+            self._initialized = True
+            self._vispy_set_current()
+            self._vispy_canvas.events.initialize()
+
+    def _vispy_warmup(self):
+        etime = time() + 0.25
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    def _vispy_set_current(self):
+        if self._surface is None:
+            return
+        # Make this the current context
+        egl.eglMakeCurrent(_EGL_DISPLAY, self._surface, self._surface,
+                           self._context)
+
+    def _vispy_swap_buffers(self):
+        if self._surface is None:
+            return
+        # Swap front and back buffer
+        egl.eglSwapBuffers(_EGL_DISPLAY, self._surface)
+
+    def _vispy_set_title(self, title):
+        pass
+
+    def _vispy_set_position(self, x, y):
+        pass
+
+    def _vispy_set_visible(self, visible):
+        pass
+
+    def _vispy_update(self):
+        # Mark that this window wants to be drawn on the next loop iter
+        self._needs_draw = True
+
+    def _vispy_close(self):
+        self._destroy_surface()
+
+    def _vispy_get_size(self):
+        if self._surface is None:
+            return
+        return self._size
+
+    def _vispy_get_position(self):
+        return 0, 0
+
+    def _on_draw(self, _id=None):
+        # This is called by the processing app
+        if self._vispy_canvas is None or self._surface is None:
+            return
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)  # (0, 0, w, h))
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend):
+
+    def __init__(self, vispy_timer):
+        BaseTimerBackend.__init__(self, vispy_timer)
+        vispy_timer._app._backend._add_timer(self)
+        self._vispy_stop()
+
+    def _vispy_start(self, interval):
+        self._interval = interval
+        self._next_time = time() + self._interval
+
+    def _vispy_stop(self):
+        self._next_time = float('inf')
+
+    def _tick(self):
+        if time() >= self._next_time:
+            self._vispy_timer._timeout()
+            self._next_time = time() + self._interval
diff --git a/vispy/app/backends/_glfw.py b/vispy/app/backends/_glfw.py
new file mode 100644
index 0000000..a057af9
--- /dev/null
+++ b/vispy/app/backends/_glfw.py
@@ -0,0 +1,478 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+vispy backend for glfw.
+"""
+
+# Note: to install GLFW on Ubuntu:
+# $ git clone git://github.com/glfw/glfw.git
+# $ cd glfw
+# $ cmake -DBUILD_SHARED_LIBS=true -DGLFW_BUILD_EXAMPLES=false \
+#         -DGLFW_BUILD_TESTS=false -DGLFW_BUILD_DOCS=false .
+# $ make
+# $ sudo make install
+# $ sudo apt-get -qq install libx11-dev
+
+# On OSX, consider using brew.
+
+from __future__ import division
+
+import atexit
+from time import sleep
+import gc
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import keys, logger
+from ...util.ptime import time
+
+
+# -------------------------------------------------------------------- init ---
+
+try:
+    from ...ext import glfw
+
+    # Map native keys to vispy keys
+    KEYMAP = {
+        glfw.GLFW_KEY_LEFT_SHIFT: keys.SHIFT,
+        glfw.GLFW_KEY_RIGHT_SHIFT: keys.SHIFT,
+        glfw.GLFW_KEY_LEFT_CONTROL: keys.CONTROL,
+        glfw.GLFW_KEY_RIGHT_CONTROL: keys.CONTROL,
+        glfw.GLFW_KEY_LEFT_ALT: keys.ALT,
+        glfw.GLFW_KEY_RIGHT_ALT: keys.ALT,
+        glfw.GLFW_KEY_LEFT_SUPER: keys.META,
+        glfw.GLFW_KEY_RIGHT_SUPER: keys.META,
+
+        glfw.GLFW_KEY_LEFT: keys.LEFT,
+        glfw.GLFW_KEY_UP: keys.UP,
+        glfw.GLFW_KEY_RIGHT: keys.RIGHT,
+        glfw.GLFW_KEY_DOWN: keys.DOWN,
+        glfw.GLFW_KEY_PAGE_UP: keys.PAGEUP,
+        glfw.GLFW_KEY_PAGE_DOWN: keys.PAGEDOWN,
+
+        glfw.GLFW_KEY_INSERT: keys.INSERT,
+        glfw.GLFW_KEY_DELETE: keys.DELETE,
+        glfw.GLFW_KEY_HOME: keys.HOME,
+        glfw.GLFW_KEY_END: keys.END,
+
+        glfw.GLFW_KEY_ESCAPE: keys.ESCAPE,
+        glfw.GLFW_KEY_BACKSPACE: keys.BACKSPACE,
+
+        glfw.GLFW_KEY_F1: keys.F1,
+        glfw.GLFW_KEY_F2: keys.F2,
+        glfw.GLFW_KEY_F3: keys.F3,
+        glfw.GLFW_KEY_F4: keys.F4,
+        glfw.GLFW_KEY_F5: keys.F5,
+        glfw.GLFW_KEY_F6: keys.F6,
+        glfw.GLFW_KEY_F7: keys.F7,
+        glfw.GLFW_KEY_F8: keys.F8,
+        glfw.GLFW_KEY_F9: keys.F9,
+        glfw.GLFW_KEY_F10: keys.F10,
+        glfw.GLFW_KEY_F11: keys.F11,
+        glfw.GLFW_KEY_F12: keys.F12,
+
+        glfw.GLFW_KEY_SPACE: keys.SPACE,
+        glfw.GLFW_KEY_ENTER: keys.ENTER,
+        '\r': keys.ENTER,
+        glfw.GLFW_KEY_TAB: keys.TAB,
+    }
+
+    BUTTONMAP = {glfw.GLFW_MOUSE_BUTTON_LEFT: 1,
+                 glfw.GLFW_MOUSE_BUTTON_RIGHT: 2,
+                 glfw.GLFW_MOUSE_BUTTON_MIDDLE: 3
+                 }
+except Exception as exp:
+    available, testable, why_not, which = False, False, str(exp), None
+else:
+    available, testable, why_not = True, True, None
+    which = 'glfw ' + str(glfw.__version__)
+
+MOD_KEYS = [keys.SHIFT, keys.ALT, keys.CONTROL, keys.META]
+_GLFW_INITIALIZED = False
+_VP_GLFW_ALL_WINDOWS = []
+
+
+def _get_glfw_windows():
+    wins = list()
+    for win in _VP_GLFW_ALL_WINDOWS:
+        if isinstance(win, CanvasBackend):
+            wins.append(win)
+    return wins
+
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=True,
+    resizable=True,
+    decorate=True,
+    fullscreen=True,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=False,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+
+def _set_config(c):
+    """Set gl configuration for GLFW """
+    glfw.glfwWindowHint(glfw.GLFW_RED_BITS, c['red_size'])
+    glfw.glfwWindowHint(glfw.GLFW_GREEN_BITS, c['green_size'])
+    glfw.glfwWindowHint(glfw.GLFW_BLUE_BITS, c['blue_size'])
+    glfw.glfwWindowHint(glfw.GLFW_ALPHA_BITS, c['alpha_size'])
+
+    glfw.glfwWindowHint(glfw.GLFW_ACCUM_RED_BITS, 0)
+    glfw.glfwWindowHint(glfw.GLFW_ACCUM_GREEN_BITS, 0)
+    glfw.glfwWindowHint(glfw.GLFW_ACCUM_BLUE_BITS, 0)
+    glfw.glfwWindowHint(glfw.GLFW_ACCUM_ALPHA_BITS, 0)
+
+    glfw.glfwWindowHint(glfw.GLFW_DEPTH_BITS, c['depth_size'])
+    glfw.glfwWindowHint(glfw.GLFW_STENCIL_BITS, c['stencil_size'])
+    #glfw.glfwWindowHint(glfw.GLFW_CONTEXT_VERSION_MAJOR, c['major_version'])
+    #glfw.glfwWindowHint(glfw.GLFW_CONTEXT_VERSION_MINOR, c['minor_version'])
+    #glfw.glfwWindowHint(glfw.GLFW_SRGB_CAPABLE, c['srgb'])
+    glfw.glfwWindowHint(glfw.GLFW_SAMPLES, c['samples'])
+    glfw.glfwWindowHint(glfw.GLFW_STEREO, c['stereo'])
+    if not c['double_buffer']:
+        raise RuntimeError('GLFW must double buffer, consider using a '
+                           'different backend, or using double buffering')
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'glfw'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._timers = list()
+
+    def _add_timer(self, timer):
+        if timer not in self._timers:
+            self._timers.append(timer)
+
+    def _vispy_get_backend_name(self):
+        return 'Glfw'
+
+    def _vispy_process_events(self):
+        glfw.glfwPollEvents()
+        for timer in self._timers:
+            timer._tick()
+        wins = _get_glfw_windows()
+        for win in wins:
+            if win._needs_draw:
+                win._needs_draw = False
+                win._on_draw()
+
+    def _vispy_run(self):
+        wins = _get_glfw_windows()
+        while all(w._id is not None and not glfw.glfwWindowShouldClose(w._id)
+                  for w in wins):
+            self._vispy_process_events()
+        self._vispy_quit()  # to clean up
+
+    def _vispy_quit(self):
+        # Close windows
+        wins = _get_glfw_windows()
+        for win in wins:
+            win._vispy_close()
+        # tear down timers
+        for timer in self._timers:
+            timer._vispy_stop()
+        self._timers = []
+
+    def _vispy_get_native_app(self):
+        global _GLFW_INITIALIZED
+        if not _GLFW_INITIALIZED:
+            if not glfw.glfwInit():  # only ever call once
+                raise OSError('Could not init glfw')
+            atexit.register(glfw.glfwTerminate)
+            _GLFW_INITIALIZED = True
+        return glfw
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(BaseCanvasBackend):
+
+    """ Glfw backend for Canvas abstract class."""
+
+    def __init__(self, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        # Init GLFW, add window hints, and create window
+        if isinstance(context, dict):
+            _set_config(context)
+            share = None
+        else:
+            share = context.value
+        glfw.glfwWindowHint(glfw.GLFW_REFRESH_RATE, 0)  # highest possible
+        glfw.glfwSwapInterval(1 if vsync else 0)
+        glfw.glfwWindowHint(glfw.GLFW_RESIZABLE, int(resize))
+        glfw.glfwWindowHint(glfw.GLFW_DECORATED, int(dec))
+        glfw.glfwWindowHint(glfw.GLFW_VISIBLE, 0)  # start out hidden
+        if fs is not False:
+            self._fullscreen = True
+            if fs is True:
+                monitor = glfw.glfwGetPrimaryMonitor()
+            else:
+                monitor = glfw.glfwGetMonitors()
+                if fs >= len(monitor):
+                    raise ValueError('fullscreen must be <= %s'
+                                     % len(monitor))
+                monitor = monitor[fs]
+            use_size = glfw.glfwGetVideoMode(monitor)[:2]
+            if use_size != size:
+                logger.warning('Requested size %s, will be ignored to '
+                               'use fullscreen mode %s' % (size, use_size))
+            size = use_size
+        else:
+            self._fullscreen = False
+            monitor = None
+
+        self._id = glfw.glfwCreateWindow(width=size[0], height=size[1],
+                                         title=title, monitor=monitor,
+                                         share=share)
+        if not self._id:
+            raise RuntimeError('Could not create window')
+        _VP_GLFW_ALL_WINDOWS.append(self)
+        self._mod = list()
+
+        # Register callbacks
+        glfw.glfwSetWindowRefreshCallback(self._id, self._on_draw)
+        glfw.glfwSetFramebufferSizeCallback(self._id, self._on_resize)
+        glfw.glfwSetKeyCallback(self._id, self._on_key_press)
+        glfw.glfwSetMouseButtonCallback(self._id, self._on_mouse_button)
+        glfw.glfwSetScrollCallback(self._id, self._on_mouse_scroll)
+        glfw.glfwSetCursorPosCallback(self._id, self._on_mouse_motion)
+        glfw.glfwSetWindowCloseCallback(self._id, self._on_close)
+        self._vispy_canvas_ = None
+        self._needs_draw = False
+        self._vispy_set_current()
+        if position is not None:
+            self._vispy_set_position(*position)
+        if show:
+            glfw.glfwShowWindow(self._id)
+        self._initialized = False
+        self._vispy_canvas = vispy_canvas
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext(self._id)
+
+    ####################################
+    # Deal with events we get from vispy
+    @property
+    def _vispy_canvas(self):
+        """ The parent canvas/window """
+        return self._vispy_canvas_
+
+    @_vispy_canvas.setter
+    def _vispy_canvas(self, vc):
+        # Init events when the property is set by Canvas
+        self._vispy_canvas_ = vc
+        if vc is not None and not self._initialized:
+            self._initialized = True
+            self._vispy_set_current()
+            self._vispy_canvas.events.initialize()
+
+    def _vispy_warmup(self):
+        etime = time() + 0.25
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    def _vispy_set_current(self):
+        if self._id is None:
+            return
+        # Make this the current context
+        glfw.glfwMakeContextCurrent(self._id)
+
+    def _vispy_swap_buffers(self):
+        if self._id is None:
+            return
+        # Swap front and back buffer
+        glfw.glfwSwapBuffers(self._id)
+
+    def _vispy_set_title(self, title):
+        if self._id is None:
+            return
+        # Set the window title. Has no effect for widgets
+        glfw.glfwSetWindowTitle(self._id, title)
+
+    def _vispy_set_size(self, w, h):
+        if self._id is None:
+            return
+        # Set size of the widget or window
+        glfw.glfwSetWindowSize(self._id, w, h)
+
+    def _vispy_set_position(self, x, y):
+        if self._id is None:
+            return
+        # Set position of the widget or window. May have no effect for widgets
+        glfw.glfwSetWindowPos(self._id, x, y)
+
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        if self._id is None:
+            return
+        if visible:
+            glfw.glfwShowWindow(self._id)
+            # this ensures that the show takes effect
+            self._vispy_update()
+        else:
+            glfw.glfwHideWindow(self._id)
+
+    def _vispy_set_fullscreen(self, fullscreen):
+        logger.warn('Cannot change fullscreen mode for GLFW backend')
+
+    def _vispy_update(self):
+        # Invoke a redraw, passing it on to the canvas
+        if self._vispy_canvas is None or self._id is None:
+            return
+        # Mark that this window wants to be drawn on the next loop iter
+        self._needs_draw = True
+
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        if self._id is not None:
+            self._vispy_canvas = None
+            # glfw.glfwSetWindowShouldClose()  # Does not really cause a close
+            self._vispy_set_visible(False)
+            self._id, id_ = None, self._id
+            glfw.glfwPollEvents()
+            glfw.glfwDestroyWindow(id_)
+            gc.collect()  # help ensure context gets destroyed
+
+    def _vispy_get_size(self):
+        if self._id is None:
+            return
+        w, h = glfw.glfwGetFramebufferSize(self._id)
+        return w, h
+
+    def _vispy_get_position(self):
+        if self._id is None:
+            return
+        x, y = glfw.glfwGetWindowPos(self._id)
+        return x, y
+
+    def _vispy_get_fullscreen(self):
+        return self._fullscreen
+
+    ##########################################
+    # Notify vispy of events triggered by GLFW
+    def _on_resize(self, _id, w, h):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.events.resize(size=(w, h))
+
+    def _on_close(self, _id):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.close()
+
+    def _on_draw(self, _id=None):
+        if self._vispy_canvas is None or self._id is None:
+            return
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)  # (0, 0, w, h))
+
+    def _on_mouse_button(self, _id, button, action, mod):
+        if self._vispy_canvas is None and self._id is not None:
+            return
+        pos = glfw.glfwGetCursorPos(self._id)
+        if button < 3:
+            # Mouse click event
+            button = BUTTONMAP.get(button, 0)
+            if action == glfw.GLFW_PRESS:
+                fun = self._vispy_mouse_press
+            elif action == glfw.GLFW_RELEASE:
+                fun = self._vispy_mouse_release
+            else:
+                return
+            fun(pos=pos, button=button, modifiers=self._mod)
+
+    def _on_mouse_scroll(self, _id, x_off, y_off):
+        if self._vispy_canvas is None and self._id is not None:
+            return
+        pos = glfw.glfwGetCursorPos(self._id)
+        delta = (float(x_off), float(y_off))
+        self._vispy_canvas.events.mouse_wheel(pos=pos, delta=delta,
+                                              modifiers=self._mod)
+
+    def _on_mouse_motion(self, _id, x, y):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_mouse_move(pos=(x, y), modifiers=self._mod)
+
+    def _on_key_press(self, _id, key, scancode, action, mod):
+        if self._vispy_canvas is None:
+            return
+        key, text = self._process_key(key)
+        if action == glfw.GLFW_PRESS:
+            fun = self._vispy_canvas.events.key_press
+            down = True
+        elif action == glfw.GLFW_RELEASE:
+            fun = self._vispy_canvas.events.key_release
+            down = False
+        else:
+            return
+        self._process_mod(key, down=down)
+        fun(key=key, text=text, modifiers=self._mod)
+
+    def _process_key(self, key):
+        if key in KEYMAP:
+            return KEYMAP[key], ''
+        elif 32 <= key <= 127:
+            return keys.Key(chr(key)), chr(key)
+        else:
+            return None, ''
+
+    def _process_mod(self, key, down):
+        """Process (possible) keyboard modifiers
+
+        GLFW provides "mod" with many callbacks, but not (critically) the
+        scroll callback, so we keep track on our own here.
+        """
+        if key in MOD_KEYS:
+            if down:
+                if key not in self._mod:
+                    self._mod.append(key)
+            elif key in self._mod:
+                self._mod.pop(self._mod.index(key))
+        return self._mod
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend):
+
+    def __init__(self, vispy_timer):
+        BaseTimerBackend.__init__(self, vispy_timer)
+        vispy_timer._app._backend._add_timer(self)
+        self._vispy_stop()
+
+    def _vispy_start(self, interval):
+        self._interval = interval
+        self._next_time = time() + self._interval
+
+    def _vispy_stop(self):
+        self._next_time = float('inf')
+
+    def _tick(self):
+        if time() >= self._next_time:
+            self._vispy_timer._timeout()
+            self._next_time = time() + self._interval
diff --git a/vispy/app/backends/_glut.py b/vispy/app/backends/_glut.py
new file mode 100644
index 0000000..afae608
--- /dev/null
+++ b/vispy/app/backends/_glut.py
@@ -0,0 +1,502 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for glut.
+"""
+
+from __future__ import division
+
+import sys
+from time import sleep, time
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import ptime, keys, logger
+
+# -------------------------------------------------------------------- init ---
+
+try:
+    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
+                 }
+
+    def _get_glut_process_func():
+        if hasattr(glut, 'glutMainLoopEvent') and bool(glut.glutMainLoopEvent):
+            func = glut.glutMainLoopEvent
+        elif hasattr(glut, 'glutCheckLoop') and bool(glut.glutCheckLoop):
+            func = glut.glutCheckLoop  # Darwin
+        else:
+            msg = ('Your implementation of GLUT does not allow '
+                   'interactivity. Consider installing freeglut.')
+            raise RuntimeError(msg)
+        return func
+except Exception as exp:
+    available, testable, why_not, which = False, False, str(exp), None
+else:
+    available, why_not, testable = True, None, True
+    try:
+        _get_glut_process_func()
+    except RuntimeError:
+        testable, why_not = False, 'No process_func'
+    which = 'from OpenGL %s' % OpenGL.__version__
+
+
+_GLUT_INITIALIZED = False
+_VP_GLUT_ALL_WINDOWS = []
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=False,
+    resizable=False,
+    decorate=False,
+    fullscreen=True,
+    context=False,
+    multi_window=False,
+    scroll=False,
+    parent=False,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+
+def _set_config(config):
+    """Set gl configuration"""
+    s = ""
+    st = '~' if sys.platform == 'darwin' else '='
+    ge = '>=' if sys.platform == 'darwin' else '='
+    s += "red%s%d " % (ge, config['red_size'])
+    s += "green%s%d " % (ge, config['green_size'])
+    s += "blue%s%d " % (ge, config['blue_size'])
+    s += "alpha%s%d " % (ge, config['alpha_size'])
+    s += "depth%s%d " % (ge, config['depth_size'])
+    s += "stencil%s%d " % (st, config['stencil_size'])
+    s += "samples%s%d " % (st, config['samples']) if config['samples'] else ""
+    s += "acca=0 " if sys.platform == 'darwin' else ""
+    if sys.platform == 'darwin':
+        s += "double=1 " if config['double_buffer'] else "single=1 "
+        s += "stereo=%d " % config['stereo']
+    else:  # freeglut
+        s += "double " if config['double_buffer'] else "single "
+        s += "stereo " if config['stereo'] else ""
+    glut.glutInitDisplayString(s.encode('ASCII'))
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'glut'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._timers = []
+
+    def _add_timer(self, timer):
+        if timer not in self._timers:
+            self._timers.append(timer)
+
+    def _vispy_get_backend_name(self):
+        return 'Glut'
+
+    def _vispy_process_events(self):
+        # Determine what function to use, if any
+        try:
+            func = _get_glut_process_func()
+        except RuntimeError:
+            self._vispy_process_events = lambda: None
+            raise
+        # Set for future use, and call!
+        self._proc_fun = func
+        self._vispy_process_events = self._process_events_and_timer
+        self._process_events_and_timer()
+
+    def _process_events_and_timer(self):
+        # helper to both call glutMainLoopEvent and tick the timers
+        self._proc_fun()
+        for timer in self._timers:
+            timer._idle_callback()
+
+    def _vispy_run(self):
+        self._vispy_get_native_app()  # Force exist
+        return glut.glutMainLoop()
+
+    def _vispy_quit(self):
+        for timer in self._timers:
+            timer._vispy_stop()
+        self._timers = []
+        if hasattr(glut, 'glutLeaveMainLoop') and bool(glut.glutLeaveMainLoop):
+            glut.glutLeaveMainLoop()
+        else:
+            for win in _VP_GLUT_ALL_WINDOWS:
+                win._vispy_close()
+
+    def _vispy_get_native_app(self):
+        global _GLUT_INITIALIZED
+        if not _GLUT_INITIALIZED:
+            glut.glutInit(['vispy'.encode('ASCII')])
+            # Prevent exit when closing window
+            try:
+                glut.glutSetOption(glut.GLUT_ACTION_ON_WINDOW_CLOSE,
+                                   glut.GLUT_ACTION_CONTINUE_EXECUTION)
+            except Exception:
+                pass
+            _GLUT_INITIALIZED = True
+        return glut
+
+
+def _set_close_fun(id_, fun):
+    # Set close function. See issue #10. For some reason, the function
+    # can still not exist even if we checked its boolean status.
+    glut.glutSetWindow(id_)
+    closeFuncSet = False
+    if bool(glut.glutWMCloseFunc):  # OSX specific test
+        try:
+            glut.glutWMCloseFunc(fun)
+            closeFuncSet = True
+        except OpenGL.error.NullFunctionError:
+            pass
+    if not closeFuncSet:
+        try:
+            glut.glutCloseFunc(fun)
+            closeFuncSet = True
+        except OpenGL.error.NullFunctionError:
+            pass
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(BaseCanvasBackend):
+
+    """ GLUT backend for Canvas abstract class."""
+
+    def __init__(self, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        _set_config(context)
+        glut.glutInitWindowSize(size[0], size[1])
+        self._id = glut.glutCreateWindow(title.encode('ASCII'))
+        if not self._id:
+            raise RuntimeError('could not create window')
+        glut.glutSetWindow(self._id)
+        _VP_GLUT_ALL_WINDOWS.append(self)
+        if fs is not False:
+            self._fullscreen = True
+            self._old_size = size
+            if fs is not True:
+                logger.warning('Cannot specify monitor for glut fullscreen, '
+                               'using default')
+            glut.glutFullScreen()
+        else:
+            self._fullscreen = False
+
+        # Cache of modifiers so we can send modifiers along with mouse motion
+        self._modifiers_cache = ()
+        self._closed = False  # Keep track whether the widget is closed
+
+        # 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_fun(self._id, self.on_close)
+        if position is not None:
+            self._vispy_set_position(*position)
+        if not show:
+            glut.glutHideWindow()
+        self._initialized = False
+        self._vispy_canvas = vispy_canvas
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext(None)  # cannot share in GLUT
+
+    def _vispy_warmup(self):
+        etime = time() + 0.4  # empirically determined :(
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    @property
+    def _vispy_canvas(self):
+        """ The parent canvas/window """
+        return self._vispy_canvas_
+
+    @_vispy_canvas.setter
+    def _vispy_canvas(self, vc):
+        # Init events when the property is set by Canvas
+        self._vispy_canvas_ = vc
+        if vc is not None and not self._initialized:
+            self._initialized = True
+            self._vispy_set_current()
+            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.encode('ASCII'))
+
+    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
+        if self._closed:
+            return
+        self._vispy_canvas = None
+        # sometimes the context is already destroyed
+        try:
+            # prevent segfaults during garbage col
+            _set_close_fun(self._id, None)
+        except Exception:
+            pass
+        self._closed = True
+        self._vispy_set_visible(False)
+        # Try destroying the widget. Not in close event, because it isnt called
+        try:
+            glut.glutDestroyWindow(self._id)
+        except Exception:
+            pass
+
+    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 _vispy_get_fullscreen(self):
+        return self._fullscreen
+
+    def _vispy_set_fullscreen(self, fullscreen):
+        old_val = self._fullscreen
+        self._fullscreen = bool(fullscreen)
+        if old_val != self._fullscreen:
+            if self._fullscreen:
+                self._old_size = self._vispy_get_size()
+                glut.glutFullScreen()
+            else:
+                self._vispy_set_size(*self._old_size)
+
+    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.close()
+
+    def on_draw(self, dummy=None):
+        if self._vispy_canvas is None:
+            return
+        #w = glut.glutGet(glut.GLUT_WINDOW_WIDTH)
+        #h = glut.glutGet(glut.GLUT_WINDOW_HEIGHT)
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(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
+
+
+# ------------------------------------------------------------------- timer ---
+
+# 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(BaseTimerBackend):
+    def __init__(self, vispy_timer):
+        BaseTimerBackend.__init__(self, vispy_timer)
+        self._schedule = list()
+        glut.glutIdleFunc(self._idle_callback)
+        # tell application instance about existence
+        vispy_timer._app._backend._add_timer(self)
+
+    def _idle_callback(self):
+        now = ptime.time()
+        new_schedule = []
+
+        # see whether there are any timers ready
+        while len(self._schedule) > 0 and self._schedule[0][0] <= now:
+            timer = self._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:
+            self._schedule.extend(new_schedule)
+            self._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 True  # glut has no native timer objects.
diff --git a/vispy/app/backends/_ipynb_static.py b/vispy/app/backends/_ipynb_static.py
new file mode 100644
index 0000000..213ea36
--- /dev/null
+++ b/vispy/app/backends/_ipynb_static.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for the IPython notebook (static approach).
+
+We aim to have:
+* ipynb_static - export visualization to a static notebook
+* ipynb_vnc - vnc-approach: render in Python, send result to JS as png
+* ipynb_webgl - send gl commands to JS and execute in webgl context
+
+"""
+
+from __future__ import division
+
+from ..base import BaseApplicationBackend, BaseCanvasBackend
+from .. import Application, Canvas
+from ...util import logger
+
+# Imports for screenshot
+from ...gloo.util import _screenshot
+from ...io import _make_png
+from base64 import b64encode
+
+# -------------------------------------------------------------------- init ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,  # But it only applies to the dummy window :P
+    size=True,  # We cannot possibly say we dont, because Canvas always sets it
+    position=True,  # Dito
+    show=True,  # Note: we don't alow this, but all scripts call show ...
+    vsync=False,
+    resizable=True,  # Yes, you can set to not be resizable (it always is)
+    decorate=False,
+    fullscreen=False,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=False,
+)
+
+
+def _set_config(c):
+    _app.backend_module._set_config(c)
+
+
+# Create our "backend" backend; The toolkit that is going to provide a
+# canvas (e.g. OpenGL context) so we can render images.
+# Note that if IPython has already loaded a GUI backend, vispy is
+# probably going to use that as well, because it prefers loaded backends.
+try:
+    # Explicitly use default (avoid using test-app)
+    _app = Application('default')
+except Exception:
+    _msg = 'ipynb_static backend relies on a core backend'
+    available, testable, why_not, which = False, False, _msg, None
+else:
+    # Try importing IPython
+    try:
+        from IPython.display import display_png
+    except Exception as exp:
+        available, testable, why_not, which = False, False, str(exp), None
+    else:
+        # Check if not GLUT, because that is going to be too unstable
+        if 'glut' in _app.backend_module.__name__:
+            _msg = 'ipynb_staatic backend refuses to work with GLUT'
+            available, testable, why_not, which = False, False, _msg, None
+        else:
+            available, testable, why_not = True, False, None
+            which = _app.backend_module.which
+
+    # Use that backend's shared context
+    KEYMAP = _app.backend_module.KEYMAP
+    SharedContext = _app.backend_module.SharedContext
+
+
+# ------------------------------------------------------------- application ---
+
+# todo: maybe trigger something in JS on any of these methods?
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._backend2 = _app._backend
+
+    def _vispy_get_backend_name(self):
+        realname = self._backend2._vispy_get_backend_name()
+        return 'ipynb_static (via %s)' % realname
+
+    def _vispy_process_events(self):
+        return self._backend2._vispy_process_events()
+
+    def _vispy_run(self):
+        pass  # We run in IPython, so we don't run!
+        #return self._backend2._vispy_run()
+
+    def _vispy_quit(self):
+        return self._backend2._vispy_quit()
+
+    def _vispy_get_native_app(self):
+        return self._backend2._vispy_get_native_app()
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(BaseCanvasBackend):
+
+    def __init__(self, *args, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+
+        # Test kwargs
+#         if kwargs['position']:
+#             raise RuntimeError('ipynb_static Canvas is not positionable')
+        if not kwargs['decorate']:
+            raise RuntimeError('ipynb_static Canvas is not decoratable')
+        if kwargs['vsync']:
+            raise RuntimeError('ipynb_static Canvas does not support vsync')
+        if kwargs['fullscreen']:
+            raise RuntimeError('ipynb_static Canvas does not support \
+                               fullscreen')
+
+        # Create real canvas. It is a backend to this backend
+        kwargs.pop('vispy_canvas', None)
+        kwargs['autoswap'] = False
+        canvas = Canvas(app=_app, **kwargs)
+        self._backend2 = canvas.native
+
+        # Connect to events of canvas to keep up to date with size and draw
+        canvas.events.draw.connect(self._on_draw)
+        canvas.events.resize.connect(self._on_resize)
+        self._initialized = False
+
+        # Show the widget
+        canvas.show()
+        # todo: hide that canvas
+
+        # Raw PNG that will be displayed on canvas.show()
+        self._im = ""
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return self._backend2._vispy_context
+
+    def _vispy_warmup(self):
+        return self._backend2._vispy_warmup()
+
+    def _vispy_set_current(self):
+        return self._backend2._vispy_set_current()
+
+    def _vispy_swap_buffers(self):
+        return self._backend2._vispy_swap_buffers()
+
+    def _vispy_set_title(self, title):
+        return self._backend2._vispy_set_title(title)
+        #logger.warn('IPython notebook canvas has not title.')
+
+    def _vispy_set_size(self, w, h):
+        return self._backend2._vispy_set_size(w, h)
+
+    def _vispy_set_position(self, x, y):
+        logger.warn('IPython notebook canvas cannot be repositioned.')
+
+    def _vispy_set_visible(self, visible):
+        #self._backend2._vispy_set_visible(visible)
+        if not visible:
+            logger.warn('IPython notebook canvas cannot be hidden.')
+        else:
+            self._vispy_update()
+            self._vispy_canvas.app.process_events()
+            self._vispy_close()
+            display_png(self._im, raw=True)
+
+    def _vispy_update(self):
+        return self._backend2._vispy_update()
+
+    def _vispy_close(self):
+        return self._backend2._vispy_close()
+        # todo: can we close on IPython side?
+
+    def _vispy_get_position(self):
+        return 0, 0
+
+    def _vispy_get_size(self):
+        return self._backend2._vispy_get_size()
+
+    def _on_resize(self, event=None):
+        # Event handler that is called by the underlying canvas
+        if self._vispy_canvas is None:
+            return
+        size = self._backend2._vispy_get_size()
+        self._vispy_canvas.events.resize(size=size)
+
+    def _on_draw(self, event=None):
+        # Event handler that is called by the underlying canvas
+        if self._vispy_canvas is None:
+            return
+        # Handle initialization
+        if not self._initialized:
+            self._initialized = True
+            self._vispy_canvas.events.initialize()
+            self._on_resize()
+        # Normal behavior
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)
+
+        # Generate base64 encoded PNG string
+        self._gen_png()
+
+    def _gen_png(self):
+        # Take the screenshot
+        screenshot = _screenshot()
+        # Convert to PNG
+        png = _make_png(screenshot)
+        # Encode base64
+        self._im = b64encode(png)
diff --git a/vispy/app/backends/_ipynb_vnc.py b/vispy/app/backends/_ipynb_vnc.py
new file mode 100644
index 0000000..e541c06
--- /dev/null
+++ b/vispy/app/backends/_ipynb_vnc.py
@@ -0,0 +1,373 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for the IPython notebook (vnc approach).
+
+We aim to have:
+* ipynb_static - export visualization to a static notebook
+* ipynb_vnc - vnc-approach: render in Python, send result to JS as png
+* ipynb_webgl - send gl commands to JS and execute in webgl context
+
+"""
+
+from __future__ import division
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend)
+from .. import Application, Canvas
+from ...util import logger
+#from ...util.event import Event  # For timer
+
+# Imports for screenshot
+# Perhaps we should refactor these to have just one import
+from ...gloo.util import _screenshot
+from ...io import _make_png
+from base64 import b64encode
+
+# Import for displaying Javascript on notebook
+import os.path as op
+
+# -------------------------------------------------------------------- init ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,  # But it only applies to the dummy window :P
+    size=True,  # We cannot possibly say we dont, because Canvas always sets it
+    position=True,  # Dito
+    show=True,  # Note: we don't alow this, but all scripts call show ...
+    vsync=False,
+    resizable=True,  # Yes, you can set to not be resizable (it always is)
+    decorate=False,
+    fullscreen=False,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=False,
+)
+
+
+def _set_config(c):
+    _app.backend_module._set_config(c)
+
+
+# Init dummy objects needed to import this module withour errors.
+# These are all overwritten with imports from IPython (on success)
+DOMWidget = object
+Unicode = Int = Float = Bool = lambda *args, **kwargs: None
+
+# Create our "backend" backend; The toolkit that is going to provide a
+# canvas (e.g. OpenGL context) so we can render images.
+# Note that if IPython has already loaded a GUI backend, vispy is
+# probably going to use that as well, because it prefers loaded backends.
+try:
+    # Explicitly use default (avoid using test-app)
+    _app = Application('default')
+except Exception:
+    _msg = 'ipynb_vnc backend relies on a core backend'
+    available, testable, why_not, which = False, False, _msg, None
+else:
+    # Try importing IPython
+    try:
+        import IPython
+        if IPython.version_info < (2,):
+            raise RuntimeError('ipynb_vnc backend need IPython version >= 2.0')
+        from IPython.html.widgets import DOMWidget
+        from IPython.utils.traitlets import Unicode, Int, Float, Bool
+        from IPython.display import display, Javascript
+        from IPython.html.nbextensions import install_nbextension
+    except Exception as exp:
+        available, testable, why_not, which = False, False, str(exp), None
+    else:
+        # Check if not GLUT, because that is going to be too unstable
+        if 'glut' in _app.backend_module.__name__:
+            _msg = 'ipynb_vnc backend refuses to work with GLUT'
+            available, testable, why_not, which = False, False, _msg, None
+        else:
+            available, testable, why_not = True, False, None
+            which = _app.backend_module.which
+        print('              NOTE: this backend requires the Chromium browser')
+    # Use that backend's shared context
+    KEYMAP = _app.backend_module.KEYMAP
+    SharedContext = _app.backend_module.SharedContext
+
+
+# ------------------------------------------------------------- application ---
+
+# todo: maybe trigger something in JS on any of these methods?
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._backend2 = _app._backend
+
+    def _vispy_get_backend_name(self):
+        realname = self._backend2._vispy_get_backend_name()
+        return 'ipynb_vnc (via %s)' % realname
+
+    def _vispy_process_events(self):
+        return self._backend2._vispy_process_events()
+
+    def _vispy_run(self):
+        pass  # We run in IPython, so we don't run!
+        #return self._backend2._vispy_run()
+
+    def _vispy_quit(self):
+        return self._backend2._vispy_quit()
+
+    def _vispy_get_native_app(self):
+        return self._backend2._vispy_get_native_app()
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(BaseCanvasBackend):
+
+    def __init__(self, *args, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+
+        # Test kwargs
+#         if kwargs['size']:
+#             raise RuntimeError('ipynb_vnc Canvas is not resizable')
+#         if kwargs['position']:
+#             raise RuntimeError('ipynb_vnc Canvas is not positionable')
+        if not kwargs['decorate']:
+            raise RuntimeError('ipynb_vnc Canvas is not decoratable (or not)')
+        if kwargs['vsync']:
+            raise RuntimeError('ipynb_vnc Canvas does not support vsync')
+        if kwargs['fullscreen']:
+            raise RuntimeError('ipynb_vnc Canvas does not support fullscreen')
+
+        # Create real canvas. It is a backend to this backend
+        kwargs.pop('vispy_canvas', None)
+        kwargs['autoswap'] = False
+        canvas = Canvas(app=_app, **kwargs)
+        self._backend2 = canvas.native
+
+        # Connect to events of canvas to keep up to date with size and draws
+        canvas.events.draw.connect(self._on_draw)
+        canvas.events.resize.connect(self._on_resize)
+        self._initialized = False
+
+        # Show the widget, we will hide it after the first time it's drawn
+        self._backend2._vispy_set_visible(True)
+        self._need_draw = False
+
+        # Prepare Javascript code by displaying on notebook
+        self._prepare_js()
+        # Create IPython Widget
+        self._widget = Widget(self._gen_event, size=canvas.size)
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return self._backend2._vispy_context
+
+    def _vispy_warmup(self):
+        return self._backend2._vispy_warmup()
+
+    def _vispy_set_current(self):
+        return self._backend2._vispy_set_current()
+
+    def _vispy_swap_buffers(self):
+        return self._backend2._vispy_swap_buffers()
+
+    def _vispy_set_title(self, title):
+        return self._backend2._vispy_set_title(title)
+        #logger.warning('IPython notebook canvas has not title.')
+
+    def _vispy_set_size(self, w, h):
+        #logger.warn('IPython notebook canvas cannot be resized.')
+        res = self._backend2._vispy_set_size(w, h)
+        self._backend2._vispy_set_visible(True)
+        return res
+
+    def _vispy_set_position(self, x, y):
+        logger.warning('IPython notebook canvas cannot be repositioned.')
+
+    def _vispy_set_visible(self, visible):
+        #self._backend2._vispy_set_visible(visible)
+        if not visible:
+            logger.warning('IPython notebook canvas cannot be hidden.')
+        else:
+            display(self._widget)
+
+    def _vispy_update(self):
+        self._need_draw = True
+        return self._backend2._vispy_update()
+
+    def _vispy_close(self):
+        self._need_draw = False
+        self._widget.quit()
+        return self._backend2._vispy_close()
+
+    def _vispy_get_position(self):
+        return 0, 0
+
+    def _vispy_get_size(self):
+        return self._backend2._vispy_get_size()
+
+    def _on_resize(self, event=None):
+        # Event handler that is called by the underlying canvas
+        if self._vispy_canvas is None:
+            return
+        size = self._backend2._vispy_get_size()
+        self._widget.size = size
+        self._vispy_canvas.events.resize(size=size)
+
+    def _on_draw(self, event=None):
+        # Event handler that is called by the underlying canvas
+        if self._vispy_canvas is None:
+            return
+        # Handle initialization
+        if not self._initialized:
+            self._initialized = True
+            #self._vispy_canvas.events.add(timer=Event)
+            self._vispy_canvas.events.initialize()
+            self._on_resize()
+
+        # We are drawn, so no need for a redraw
+        self._need_draw = False
+
+        # We hide the widget once it has received a paint event. So at
+        # initialization and after a resize the widget is briefly visible.
+        # Now that it is hidden the widget is unlikely to receive paint
+        # events anymore, so we need to force repaints from now on, via
+        # a trigger from JS.
+        self._backend2._vispy_set_visible(False)
+
+        # Normal behavior
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)
+        # Save the encoded screenshot image to widget
+        self._save_screenshot()
+
+    def _save_screenshot(self):
+        # Take the screenshot
+        img = _screenshot()
+        # Convert to PNG and encode
+        self._widget.value = b64encode(_make_png(img))
+
+    # Generate vispy events according to upcoming JS events
+    def _gen_event(self, ev):
+        if self._vispy_canvas is None:
+            return
+
+        ev = ev.get("event")
+        # Parse and generate event
+        if ev.get("name") == "MouseEvent":
+            mouse = ev.get("properties")
+            # Generate
+            if mouse.get("type") == "mouse_move":
+                self._vispy_mouse_move(native=mouse,
+                                       pos=mouse.get("pos"),
+                                       modifiers=mouse.get("modifiers"),
+                                       )
+            elif mouse.get("type") == "mouse_press":
+                self._vispy_mouse_press(native=mouse,
+                                        pos=mouse.get("pos"),
+                                        button=mouse.get("button"),
+                                        modifiers=mouse.get("modifiers"),
+                                        )
+            elif mouse.get("type") == "mouse_release":
+                self._vispy_mouse_release(native=mouse,
+                                          pos=mouse.get("pos"),
+                                          button=mouse.get("button"),
+                                          modifiers=mouse.get("modifiers"),
+                                          )
+            elif mouse.get("type") == "mouse_wheel":
+                self._vispy_canvas.events.mouse_wheel(native=mouse,
+                                                      delta=mouse.get("delta"),
+                                                      pos=mouse.get("pos"),
+                                                      modifiers=mouse.get
+                                                      ("modifiers"),
+                                                      )
+        elif ev.get("name") == "KeyEvent":
+            key = ev.get("properties")
+            if key.get("type") == "key_press":
+                self._vispy_canvas.events.key_press(native=key,
+                                                    key=key.get("key"),
+                                                    text=key.get("text"),
+                                                    modifiers=key.get
+                                                    ("modifiers"),
+                                                    )
+            elif key.get("type") == "key_release":
+                self._vispy_canvas.events.key_release(native=key,
+                                                      key=key.get("key"),
+                                                      text=key.get("text"),
+                                                      modifiers=key.get
+                                                      ("modifiers"),
+                                                      )
+        elif ev.get("name") == "PollEvent":  # Ticking from front-end (JS)
+            # Allthough the event originates from JS, this is basically
+            # a poll event from IPyhon's event loop, which we use to
+            # update the backend app and draw stuff if necessary. If we
+            # succeed to make IPython process GUI app events directly,
+            # this "JS timer" should not be necessary.
+            self._vispy_canvas.app.process_events()
+            if self._need_draw:
+                self._on_draw()
+            # Generate a timer event on every poll from JS
+            # AK: no, just use app.Timer as usual!
+            #self._vispy_canvas.events.timer(type="timer")
+
+    def _prepare_js(self):
+        pkgdir = op.dirname(__file__)
+        install_nbextension([op.join(pkgdir, '../../html/static/js')])
+        script = 'IPython.load_extensions("js/vispy");'
+        display(Javascript(script))
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend):
+
+    def __init__(self, vispy_timer):
+        self._backend2 = _app.backend_module.TimerBackend(vispy_timer)
+
+    def _vispy_start(self, interval):
+        return self._backend2._vispy_start(interval)
+
+    def _vispy_stop(self):
+        return self._backend2._vispy_stop()
+
+    def _vispy_timeout(self):
+        return self._backend2._vispy_timeout()
+
+
+# ---------------------------------------------------------- IPython Widget ---
+
+class Widget(DOMWidget):
+    _view_name = Unicode("Widget", sync=True)
+
+    # Define the custom state properties to sync with the front-end
+    format = Unicode('png', sync=True)
+    width = Int(sync=True)
+    height = Int(sync=True)
+    interval = Float(sync=True)
+    is_closing = Bool(sync=True)
+    value = Unicode(sync=True)
+
+    def __init__(self, gen_event, **kwargs):
+        super(Widget, self).__init__(**kwargs)
+        self.size = kwargs["size"]
+        self.interval = 50.0
+        self.gen_event = gen_event
+        self.on_msg(self._handle_event_msg)
+
+    def _handle_event_msg(self, _, content):
+        # If closing, don't bother generating the event
+        if not self.is_closing:
+            self.gen_event(content)
+
+    @property
+    def size(self):
+        return self.width, self.height
+
+    @size.setter
+    def size(self, size):
+        self.width, self.height = size
+
+    def quit(self):
+        self.is_closing = True
+        self.close()
diff --git a/vispy/app/backends/_pyglet.py b/vispy/app/backends/_pyglet.py
new file mode 100644
index 0000000..7942483
--- /dev/null
+++ b/vispy/app/backends/_pyglet.py
@@ -0,0 +1,453 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for pyglet.
+"""
+
+from __future__ import division
+
+from distutils.version import LooseVersion
+from time import sleep
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import keys
+from ...util.ptime import time
+
+
+# -------------------------------------------------------------------- init ---
+
+try:
+    import pyglet
+    version = pyglet.version
+    if LooseVersion(version) < LooseVersion('1.2'):
+        help_ = ('You can install the latest pyglet using:\n    '
+                 'pip install http://pyglet.googlecode.com/archive/tip.zip')
+        raise ImportError('Pyglet version too old (%s), need >= 1.2\n%s'
+                          % (version, help_))
+
+    # 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
+                 }
+except Exception as exp:
+    available, testable, why_not, which = False, False, str(exp), None
+
+    class _Window(object):
+        pass
+else:
+    available, testable, why_not = True, True, None
+    which = 'pyglet ' + str(pyglet.version)
+    _Window = pyglet.window.Window
+
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=True,
+    resizable=True,
+    decorate=True,
+    fullscreen=True,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=False,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+
+def _set_config(config):
+    """Set gl configuration"""
+    pyglet_config = pyglet.gl.Config()
+
+    pyglet_config.red_size = config['red_size']
+    pyglet_config.green_size = config['green_size']
+    pyglet_config.blue_size = config['blue_size']
+    pyglet_config.alpha_size = config['alpha_size']
+
+    pyglet_config.accum_red_size = 0
+    pyglet_config.accum_green_size = 0
+    pyglet_config.accum_blue_size = 0
+    pyglet_config.accum_alpha_size = 0
+
+    pyglet_config.depth_size = config['depth_size']
+    pyglet_config.stencil_size = config['stencil_size']
+    pyglet_config.double_buffer = config['double_buffer']
+    pyglet_config.stereo = config['stereo']
+    pyglet_config.samples = config['samples']
+    return pyglet_config
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'pyglet'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+
+    def _vispy_get_backend_name(self):
+        return 'Pyglet'
+
+    def _vispy_process_events(self):
+        # pyglet.app.platform_event_loop.step(0.0)
+        pyglet.clock.tick()
+        for window in pyglet.app.windows:
+            window.switch_to()
+            window.dispatch_events()
+            window.dispatch_event('on_draw')
+
+    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
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(_Window, BaseCanvasBackend):
+
+    """ Pyglet backend for Canvas abstract class."""
+
+    def __init__(self, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        self._vispy_canvas = vispy_canvas
+        if not isinstance(context, (dict, SharedContext)):
+            raise TypeError('context must be a dict or pyglet SharedContext')
+        if not isinstance(context, SharedContext):
+            config = _set_config(context)  # transform to Pyglet config
+        else:
+            # contexts are shared by default in Pyglet, so we shouldn't need
+            # to do anything to share them...
+            config = None
+        style = (pyglet.window.Window.WINDOW_STYLE_DEFAULT if dec else
+                 pyglet.window.Window.WINDOW_STYLE_BORDERLESS)
+        # 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
+        if fs is not False:
+            screen = pyglet.window.get_platform().get_default_display()
+            self._vispy_fullscreen = True
+            if fs is True:
+                self._vispy_screen = screen.get_default_screen()
+            else:
+                screen = screen.get_screens()
+                if fs >= len(screen):
+                    raise RuntimeError('fullscreen must be < %s'
+                                       % len(screen))
+                self._vispy_screen = screen[fs]
+        else:
+            self._vispy_fullscreen = False
+            self._vispy_screen = None
+        self._initialize_sent = False
+        pyglet.window.Window.__init__(self, width=size[0], height=size[1],
+                                      caption=title, visible=show,
+                                      config=config, vsync=vsync,
+                                      resizable=resize, style=style,
+                                      screen=self._vispy_screen)
+        if position is not None:
+            self._vispy_set_position(*position)
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext(None)
+
+    def _vispy_warmup(self):
+        etime = time() + 0.1
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    # 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_draw_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_draw_func, 0.0)
+
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        # In Pyglet close is equivalent to destroy (window becomes invalid)
+        self._vispy_canvas = None
+        self.close()
+
+    def _vispy_get_size(self):
+        w, h = self.get_size()
+        return w, h
+
+    def _vispy_get_position(self):
+        x, y = self.get_location()
+        return x, y
+
+    def _vispy_get_fullscreen(self):
+        return self._vispy_fullscreen
+
+    def _vispy_set_fullscreen(self, fullscreen):
+        self._vispy_fullscreen = bool(fullscreen)
+        self.set_fullscreen(self._vispy_fullscreen, self._vispy_screen)
+
+    def on_show(self):
+        if self._vispy_canvas is None:
+            return
+        if not self._initialize_sent:
+            self._initialize_sent = True
+            self._vispy_set_current()
+            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.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_draw_func(self, dummy=None):
+        if not self._draw_ok or self._vispy_canvas is None:
+            return
+        # (0, 0, self.width, self.height))
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)
+
+    def on_mouse_press(self, x, y, button, modifiers=None):
+        if self._vispy_canvas is None:
+            return
+        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)
+        # 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
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend):
+
+    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/_pyqt4.py b/vispy/app/backends/_pyqt4.py
new file mode 100644
index 0000000..46cebc8
--- /dev/null
+++ b/vispy/app/backends/_pyqt4.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" PyQt4 proxy backend for the qt backend. 
+"""
+
+import sys
+from .. import backends
+from ...util import logger
+
+try:
+    # Try importing
+    from PyQt4 import QtGui, QtCore, QtOpenGL  # noqa
+except Exception as exp:
+    # Fail: this backend cannot be used
+    available, testable, why_not, which = False, False, str(exp), None
+else:
+    # Success
+    available, testable, why_not = True, True, None
+    has_uic = True
+    which = ('PyQt4', QtCore.PYQT_VERSION_STR, QtCore.QT_VERSION_STR)
+    # Remove _qt module to force an import even if it was already imported
+    sys.modules.pop(__name__.replace('_pyqt4', '_qt'), None)
+    # Import _qt. Keep a ref to the module object!
+    if backends.qt_lib is None:
+        backends.qt_lib = 'pyqt4'  # Signal to _qt what it should import
+        from . import _qt  # noqa
+        from ._qt import *  # noqa
+    else:
+        logger.info('%s already imported, cannot switch to %s'
+                    % (backends.qt_lib, 'pyqt4'))
diff --git a/vispy/app/backends/_pyside.py b/vispy/app/backends/_pyside.py
new file mode 100644
index 0000000..762d96c
--- /dev/null
+++ b/vispy/app/backends/_pyside.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" PySide proxy backend for the qt backend. 
+"""
+
+import sys
+from .. import backends
+from ...util import logger
+
+try:
+    # Try importing
+    from PySide import QtGui, QtCore, QtOpenGL  # noqa
+except Exception as exp:
+    # Fail: this backend cannot be used
+    available, testable, why_not, which = False, False, str(exp), None
+else:
+    # Success
+    available, testable, why_not = True, True, None
+    has_uic = False
+    import PySide
+    which = ('PySide', PySide.__version__, QtCore.__version__)
+    # Remove _qt module to force an import even if it was already imported
+    sys.modules.pop(__name__.replace('_pyside', '_qt'), None)
+    # Import _qt. Keep a ref to the module object!
+    if backends.qt_lib is None:
+        backends.qt_lib = 'pyside'  # Signal to _qt what it should import
+        from . import _qt  # noqa
+        from ._qt import *  # noqa
+    else:
+        logger.info('%s already imported, cannot switch to %s'
+                    % (backends.qt_lib, 'PySide'))
diff --git a/vispy/app/backends/_qt.py b/vispy/app/backends/_qt.py
new file mode 100644
index 0000000..c0e0f8d
--- /dev/null
+++ b/vispy/app/backends/_qt.py
@@ -0,0 +1,440 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Base code for the PySide and PyQt4 backends. Note that this is *not*
+(anymore) a backend by itself! One has to explicitly use either PySide
+or PyQt4. Note that the automatic backend selection prefers a GUI
+toolkit that is already imported.
+
+The _pyside and _pyqt4 modules will import * from this module, and also
+keep a ref to the module object. Note that if both the PySide and PyQt4
+backend are used, this module is actually reloaded. This is a sorts of
+poor mans "subclassing" to get a working version for both backends using
+the same code.
+
+Note that it is strongly discouraged to use the PySide and PyQt4
+backends simultaneously. It is known to cause unpredictable behavior
+and segfaults.
+"""
+
+from __future__ import division
+
+from time import sleep, time
+from ...util import logger
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import keys
+from ...ext.six import text_type
+
+from . import qt_lib
+
+
+# -------------------------------------------------------------------- init ---
+
+# Get what qt lib to try. This tells us wheter this module is imported
+# via _pyside or _pyqt4
+if qt_lib == 'pyqt4':
+    from PyQt4 import QtGui, QtCore, QtOpenGL
+elif qt_lib == 'pyside':
+    from PySide import QtGui, QtCore, QtOpenGL
+elif qt_lib:
+    raise RuntimeError("Invalid value for qt_lib %r." % qt_lib)
+else:
+    raise RuntimeError("Module backends._qt should not be imported directly.")
+
+# 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}
+
+
+# Properly log Qt messages
+# Also, ignore spam about tablet input
+def message_handler(msg_type, msg):
+    if msg == ("QCocoaView handleTabletEvent: This tablet device is "
+               "unknown (received no proximity event for it). Discarding "
+               "event."):
+        return
+    else:
+        logger.warning(msg)
+
+QtCore.qInstallMsgHandler(message_handler)
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=True,
+    resizable=True,
+    decorate=True,
+    fullscreen=True,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=True,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+def _set_config(c):
+    """Set the OpenGL configuration"""
+    glformat = QtOpenGL.QGLFormat()
+    glformat.setRedBufferSize(c['red_size'])
+    glformat.setGreenBufferSize(c['green_size'])
+    glformat.setBlueBufferSize(c['blue_size'])
+    glformat.setAlphaBufferSize(c['alpha_size'])
+    glformat.setAccum(False)
+    glformat.setRgba(True)
+    glformat.setDoubleBuffer(True if c['double_buffer'] else False)
+    glformat.setDepth(True if c['depth_size'] else False)
+    glformat.setDepthBufferSize(c['depth_size'] if c['depth_size'] else 0)
+    glformat.setStencil(True if c['stencil_size'] else False)
+    glformat.setStencilBufferSize(c['stencil_size'] if c['stencil_size']
+                                  else 0)
+    glformat.setSampleBuffers(True if c['samples'] else False)
+    glformat.setSamples(c['samples'] if c['samples'] else 0)
+    glformat.setStereo(c['stereo'])
+    return glformat
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'qt'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__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 vispy object,
+        # or an application may produce error when closed
+        QtGui._qApp = app
+        # Return
+        return app
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(QtOpenGL.QGLWidget, BaseCanvasBackend):
+
+    """Qt backend for Canvas abstract class."""
+
+    def __init__(self, *args, **kwargs):
+        self._initialized = False
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        self._vispy_canvas = vispy_canvas
+        if isinstance(context, dict):
+            glformat = _set_config(context)
+            glformat.setSwapInterval(1 if vsync else 0)
+            widget = kwargs.pop('shareWidget', None)
+        else:
+            glformat = QtOpenGL.QGLFormat.defaultFormat()
+            if 'shareWidget' in kwargs:
+                raise RuntimeError('cannot use vispy to share context and '
+                                   'use built-in shareWidget')
+            widget = context.value
+        f = QtCore.Qt.Widget if dec else QtCore.Qt.FramelessWindowHint
+
+        # first arg can be glformat, or a shared context
+        QtOpenGL.QGLWidget.__init__(self, glformat, parent, widget, f)
+        self._initialized = True
+        if not self.isValid():
+            raise RuntimeError('context could not be created')
+        self.setAutoBufferSwap(False)  # to make consistent with other backends
+        self.setMouseTracking(True)
+        self._vispy_set_title(title)
+        self._vispy_set_size(*size)
+        if fs is not False:
+            if fs is not True:
+                logger.warning('Cannot specify monitor number for Qt '
+                               'fullscreen, using default')
+            self._fullscreen = True
+        else:
+            self._fullscreen = False
+        if not resize:
+            self.setFixedSize(self.size())
+        if position is not None:
+            self._vispy_set_position(*position)
+        self._init_show = show
+
+    def _vispy_init(self):
+        """Do actions that require self._vispy_canvas._backend to be set"""
+        if self._init_show:
+            self._vispy_set_visible(True)
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext(self)
+
+    def _vispy_warmup(self):
+        etime = time() + 0.25
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    def _vispy_set_current(self):
+        # Make this the current context
+        if self._vispy_canvas is None:
+            return
+        if self.isValid():
+            self.makeCurrent()
+
+    def _vispy_swap_buffers(self):
+        # Swap front and back buffer
+        if self._vispy_canvas is None:
+            return
+        self.swapBuffers()
+
+    def _vispy_set_title(self, title):
+        # Set the window title. Has no effect for widgets
+        if self._vispy_canvas is None:
+            return
+        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:
+            if self._fullscreen:
+                self.showFullScreen()
+            else:
+                self.showNormal()
+        else:
+            self.hide()
+
+    def _vispy_set_fullscreen(self, fullscreen):
+        self._fullscreen = bool(fullscreen)
+        self._vispy_set_visible(True)
+
+    def _vispy_get_fullscreen(self):
+        return self._fullscreen
+
+    def _vispy_update(self):
+        if self._vispy_canvas is None:
+            return
+        # Invoke a redraw
+        self.update()
+
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        self.close()
+        self.doneCurrent()
+        self.context().reset()
+
+    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
+        # (0, 0, self.width(), self.height()))
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)
+
+    def closeEvent(self, ev):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.close()
+
+    def sizeHint(self):
+        return self.size()
+
+    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._keyEvent(self._vispy_canvas.events.key_press, ev)
+
+    def keyReleaseEvent(self, ev):
+        self._keyEvent(self._vispy_canvas.events.key_release, ev)
+
+    def _keyEvent(self, func, ev):
+        # evaluates the keycode of qt, and transform to vispy key.
+        key = int(ev.key())
+        if key in KEYMAP:
+            key = KEYMAP[key]
+        elif key >= 32 and key <= 127:
+            key = keys.Key(chr(key))
+        else:
+            key = None
+        mod = self._modifiers(ev)
+        func(native=ev, key=key, text=text_type(ev.text()), modifiers=mod)
+
+    def _modifiers(self, event):
+        # Convert the QT modifier state into a tuple of active modifier keys.
+        mod = ()
+        qtmod = event.modifiers()
+        for q, v in ([QtCore.Qt.ShiftModifier, keys.SHIFT],
+                     [QtCore.Qt.ControlModifier, keys.CONTROL],
+                     [QtCore.Qt.AltModifier, keys.ALT],
+                     [QtCore.Qt.MetaModifier, keys.META]):
+            if q & qtmod:
+                mod += (v,)
+        return mod
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend, QtCore.QTimer):
+
+    def __init__(self, vispy_timer):
+        if QtGui.QApplication.instance() is None:
+            global QAPP
+            QAPP = QtGui.QApplication([])
+        BaseTimerBackend.__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/_sdl2.py b/vispy/app/backends/_sdl2.py
new file mode 100644
index 0000000..fde660a
--- /dev/null
+++ b/vispy/app/backends/_sdl2.py
@@ -0,0 +1,458 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+vispy backend for sdl2.
+"""
+
+from __future__ import division
+
+import atexit
+import ctypes
+from time import sleep
+import warnings
+import gc
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import keys, logger
+from ...util.ptime import time
+
+
+# -------------------------------------------------------------------- init ---
+
+try:
+    with warnings.catch_warnings(record=True):  # can throw warnings
+        import sdl2
+        import sdl2.ext
+
+    # Map native keys to vispy keys
+    KEYMAP = {
+        # http://www.ginkgobitter.org/sdl/?SDL_Keycode
+        sdl2.SDLK_LSHIFT: keys.SHIFT,
+        sdl2.SDLK_RSHIFT: keys.SHIFT,
+        sdl2.SDLK_LCTRL: keys.CONTROL,
+        sdl2.SDLK_RCTRL: keys.CONTROL,
+        sdl2.SDLK_LALT: keys.ALT,
+        sdl2.SDLK_RALT: keys.ALT,
+        sdl2.SDLK_LGUI: keys.META,
+        sdl2.SDLK_RGUI: keys.META,
+
+        sdl2.SDLK_LEFT: keys.LEFT,
+        sdl2.SDLK_UP: keys.UP,
+        sdl2.SDLK_RIGHT: keys.RIGHT,
+        sdl2.SDLK_DOWN: keys.DOWN,
+        sdl2.SDLK_PAGEUP: keys.PAGEUP,
+        sdl2.SDLK_PAGEDOWN: keys.PAGEDOWN,
+
+        sdl2.SDLK_INSERT: keys.INSERT,
+        sdl2.SDLK_DELETE: keys.DELETE,
+        sdl2.SDLK_HOME: keys.HOME,
+        sdl2.SDLK_END: keys.END,
+
+        sdl2.SDLK_ESCAPE: keys.ESCAPE,
+        sdl2.SDLK_BACKSPACE: keys.BACKSPACE,
+
+        sdl2.SDLK_F1: keys.F1,
+        sdl2.SDLK_F2: keys.F2,
+        sdl2.SDLK_F3: keys.F3,
+        sdl2.SDLK_F4: keys.F4,
+        sdl2.SDLK_F5: keys.F5,
+        sdl2.SDLK_F6: keys.F6,
+        sdl2.SDLK_F7: keys.F7,
+        sdl2.SDLK_F8: keys.F8,
+        sdl2.SDLK_F9: keys.F9,
+        sdl2.SDLK_F10: keys.F10,
+        sdl2.SDLK_F11: keys.F11,
+        sdl2.SDLK_F12: keys.F12,
+
+        sdl2.SDLK_SPACE: keys.SPACE,
+        sdl2.SDLK_RETURN: keys.ENTER,
+        sdl2.SDLK_TAB: keys.TAB,
+    }
+
+    BUTTONMAP = {sdl2.SDL_BUTTON_LEFT: 1,
+                 sdl2.SDL_BUTTON_MIDDLE: 2,
+                 sdl2.SDL_BUTTON_RIGHT: 3
+                 }
+except Exception as exp:
+    available, testable, why_not, which = False, False, str(exp), None
+else:
+    available, testable, why_not = True, True, None
+    which = 'sdl2 %d.%d.%d' % sdl2.version_info[:3]
+
+_SDL2_INITIALIZED = False
+_VP_SDL2_ALL_WINDOWS = {}
+
+
+def _get_sdl2_windows():
+    return list(_VP_SDL2_ALL_WINDOWS.values())
+
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=True,
+    resizable=True,
+    decorate=True,
+    fullscreen=True,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=False,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+
+def _set_config(c):
+    """Set gl configuration for SDL2"""
+    func = sdl2.SDL_GL_SetAttribute
+    func(sdl2.SDL_GL_RED_SIZE, c['red_size'])
+    func(sdl2.SDL_GL_GREEN_SIZE, c['green_size'])
+    func(sdl2.SDL_GL_BLUE_SIZE, c['blue_size'])
+    func(sdl2.SDL_GL_ALPHA_SIZE, c['alpha_size'])
+    func(sdl2.SDL_GL_DEPTH_SIZE, c['depth_size'])
+    func(sdl2.SDL_GL_STENCIL_SIZE, c['stencil_size'])
+    func(sdl2.SDL_GL_DOUBLEBUFFER, 1 if c['double_buffer'] else 0)
+    samps = c['samples']
+    func(sdl2.SDL_GL_MULTISAMPLEBUFFERS, 1 if samps > 0 else 0)
+    func(sdl2.SDL_GL_MULTISAMPLESAMPLES, samps if samps > 0 else 0)
+    func(sdl2.SDL_GL_STEREO, c['stereo'])
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'sdl2'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._timers = list()
+
+    def _add_timer(self, timer):
+        if timer not in self._timers:
+            self._timers.append(timer)
+
+    def _vispy_get_backend_name(self):
+        return 'SDL2'
+
+    def _vispy_process_events(self):
+        events = sdl2.ext.get_events()
+        while len(events) > 0:
+            for event in events:
+                _id = event.window.windowID
+                if _id in _VP_SDL2_ALL_WINDOWS:
+                    win = _VP_SDL2_ALL_WINDOWS[_id]
+                    win._on_event(event)
+            events = sdl2.ext.get_events()
+        for timer in self._timers:
+            timer._tick()
+        wins = _get_sdl2_windows()
+        for win in wins:
+            if win._needs_draw:
+                win._needs_draw = False
+                win._on_draw()
+
+    def _vispy_run(self):
+        wins = _get_sdl2_windows()
+        while all(w._id is not None for w in wins):
+            self._vispy_process_events()
+        self._vispy_quit()  # to clean up
+
+    def _vispy_quit(self):
+        # Close windows
+        wins = _get_sdl2_windows()
+        for win in wins:
+            win._vispy_close()
+        # tear down timers
+        for timer in self._timers:
+            timer._vispy_stop()
+        self._timers = []
+
+    def _vispy_get_native_app(self):
+        global _SDL2_INITIALIZED
+        if not _SDL2_INITIALIZED:
+            sdl2.ext.init()
+            atexit.register(sdl2.ext.quit)
+            _SDL2_INITIALIZED = True
+        return sdl2
+
+
+# ------------------------------------------------------------------ canvas ---
+
+class CanvasBackend(BaseCanvasBackend):
+
+    """ SDL2 backend for Canvas abstract class."""
+
+    def __init__(self, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        # Init SDL2, add window hints, and create window
+        if isinstance(context, dict):
+            _set_config(context)
+            share = None
+        else:
+            share = context.value
+            sdl2.SDL_GL_MakeCurrent(*share)  # old window must be current
+            sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1)
+
+        sdl2.SDL_GL_SetSwapInterval(1 if vsync else 0)
+        flags = sdl2.SDL_WINDOW_OPENGL
+        flags |= sdl2.SDL_WINDOW_SHOWN  # start out shown
+        flags |= sdl2.SDL_WINDOW_ALLOW_HIGHDPI
+        flags |= sdl2.SDL_WINDOW_RESIZABLE if resize else 0
+        flags |= sdl2.SDL_WINDOW_BORDERLESS if not dec else 0
+        if fs is not False:
+            self._fullscreen = True
+            if fs is not True:
+                logger.warning('Cannot specify monitor number for SDL2 '
+                               'fullscreen, using default')
+            flags |= sdl2.SDL_WINDOW_FULLSCREEN_DESKTOP
+        else:
+            self._fullscreen = False
+        self._mods = list()
+        if position is None:
+            position = [sdl2.SDL_WINDOWPOS_UNDEFINED] * 2
+        self._id = sdl2.ext.Window(title, size, position, flags)
+        if not self._id.window:
+            raise RuntimeError('Could not create window')
+        if share is None:
+            self._native_context = sdl2.SDL_GL_CreateContext(self._id.window)
+        else:
+            self._native_context = sdl2.SDL_GL_CreateContext(share[0])
+        self._sdl_id = sdl2.SDL_GetWindowID(self._id.window)
+        _VP_SDL2_ALL_WINDOWS[self._sdl_id] = self
+        self._vispy_canvas_ = None
+        self._needs_draw = False
+        self._vispy_set_current()
+        if not show:
+            self._vispy_set_visible(False)
+        self._initialized = False
+        self._vispy_canvas = vispy_canvas
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext((self._id.window, self._native_context))
+
+    ####################################
+    # Deal with events we get from vispy
+    @property
+    def _vispy_canvas(self):
+        """ The parent canvas/window """
+        return self._vispy_canvas_
+
+    @_vispy_canvas.setter
+    def _vispy_canvas(self, vc):
+        # Init events when the property is set by Canvas
+        self._vispy_canvas_ = vc
+        if vc is not None and not self._initialized:
+            self._initialized = True
+            self._vispy_set_current()
+            self._vispy_canvas.events.initialize()
+
+    def _vispy_warmup(self):
+        etime = time() + 0.1
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    def _vispy_set_current(self):
+        if self._id is None:
+            return
+        # Make this the current context
+        sdl2.SDL_GL_MakeCurrent(self._id.window, self._native_context)
+
+    def _vispy_swap_buffers(self):
+        if self._id is None:
+            return
+        # Swap front and back buffer
+        sdl2.SDL_GL_SwapWindow(self._id.window)
+
+    def _vispy_set_title(self, title):
+        if self._id is None:
+            return
+        # Set the window title. Has no effect for widgets
+        sdl2.SDL_SetWindowTitle(self._id.window, title.encode('UTF-8'))
+
+    def _vispy_set_size(self, w, h):
+        if self._id is None:
+            return
+        # Set size of the widget or window
+        sdl2.SDL_SetWindowSize(self._id.window, w, h)
+
+    def _vispy_set_position(self, x, y):
+        if self._id is None:
+            return
+        # Set position of the widget or window. May have no effect for widgets
+        sdl2.SDL_SetWindowPosition(self._id.window, x, y)
+
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        if self._id is None:
+            return
+        if visible:
+            self._id.show()
+            # this ensures that the show takes effect
+            self._vispy_update()
+        else:
+            self._id.hide()
+
+    def _vispy_update(self):
+        # Invoke a redraw, passing it on to the canvas
+        if self._vispy_canvas is None or self._id is None:
+            return
+        # Mark that this window wants to be drawn on the next loop iter
+        self._needs_draw = True
+
+    def _vispy_close(self):
+        # Force the window or widget to shut down
+        if self._id is not None:
+            _id = self._id.window
+            self._vispy_canvas = None
+            self._id = None
+            sdl2.SDL_DestroyWindow(_id)
+            del _VP_SDL2_ALL_WINDOWS[self._sdl_id]
+            self._sdl_id = None
+            gc.collect()  # enforce gc to help context get destroyed
+
+    def _vispy_get_size(self):
+        if self._id is None:
+            return
+        w, h = ctypes.c_int(), ctypes.c_int()
+        sdl2.SDL_GetWindowSize(self._id.window,
+                               ctypes.byref(w), ctypes.byref(h))
+        w, h = w.value, h.value
+        return w, h
+
+    def _vispy_get_fullscreen(self):
+        return self._fullscreen
+
+    def _vispy_set_fullscreen(self, fullscreen):
+        self._fullscreen = bool(fullscreen)
+        flags = sdl2.SDL_WINDOW_FULLSCREEN_DESKTOP if self._fullscreen else 0
+        sdl2.SDL_SetWindowFullscreen(self._id.window, flags)
+
+    def _vispy_get_position(self):
+        if self._id is None:
+            return
+        x, y = ctypes.c_int(), ctypes.c_int()
+        sdl2.SDL_GetWindowPosition(self._id.window,
+                                   ctypes.byref(x), ctypes.byref(y))
+        x, y = x.value, y.value
+        return x, y
+
+    ##########################################
+    # Notify vispy of events triggered by SDL2
+    def _get_mouse_position(self):
+        if self._id is None:
+            return (0, 0)
+        x, y = ctypes.c_int(), ctypes.c_int()
+        sdl2.SDL_GetMouseState(ctypes.byref(x), ctypes.byref(y))
+        return x.value, y.value
+
+    def _on_draw(self):
+        if self._vispy_canvas is None or self._id is None:
+            return
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)  # (0, 0, w, h))
+
+    def _on_event(self, event):
+        if self._vispy_canvas is None:
+            return
+        # triage event to proper handler
+        if event.type == sdl2.SDL_QUIT:
+            self._vispy_canvas.close()
+        elif event.type == sdl2.SDL_WINDOWEVENT:
+            if event.window.event == sdl2.SDL_WINDOWEVENT_RESIZED:
+                w, h = event.window.data1, event.window.data2
+                self._vispy_canvas.events.resize(size=(w, h))
+            elif event.window.event == sdl2.SDL_WINDOWEVENT_CLOSE:
+                self._vispy_canvas.close()
+        elif event.type == sdl2.SDL_MOUSEMOTION:
+            x, y = event.motion.x, event.motion.y
+            self._vispy_mouse_move(pos=(x, y), modifiers=self._mods)
+        elif event.type in (sdl2.SDL_MOUSEBUTTONDOWN,
+                            sdl2.SDL_MOUSEBUTTONUP):
+            x, y = event.button.x, event.button.y
+            button = event.button.button
+            if button in BUTTONMAP:
+                button = BUTTONMAP.get(button, 0)
+                if event.type == sdl2.SDL_MOUSEBUTTONDOWN:
+                    func = self._vispy_mouse_press
+                else:
+                    func = self._vispy_mouse_release
+                func(pos=(x, y), button=button, modifiers=self._mods)
+        elif event.type == sdl2.SDL_MOUSEWHEEL:
+            pos = self._get_mouse_position()
+            delta = float(event.wheel.x), float(event.wheel.y)
+            self._vispy_canvas.events.mouse_wheel(pos=pos, delta=delta,
+                                                  modifiers=self._mods)
+        elif event.type in (sdl2.SDL_KEYDOWN, sdl2.SDL_KEYUP):
+            down = (event.type == sdl2.SDL_KEYDOWN)
+            keysym = event.key.keysym
+            mods = keysym.mod
+            key = keysym.sym
+            self._process_mod(mods, down)
+            if key in KEYMAP:
+                key, text = KEYMAP[key], ''
+            elif key >= 32 and key <= 127:
+                key, text = keys.Key(chr(key)), chr(key)
+            else:
+                key, text = None, ''
+            if down:
+                fun = self._vispy_canvas.events.key_press
+            else:
+                fun = self._vispy_canvas.events.key_release
+            fun(key=key, text=text, modifiers=self._mods)
+
+    def _process_mod(self, key, down):
+        _modifiers = list()
+        if key & (sdl2.SDLK_LSHIFT | sdl2.SDLK_RSHIFT):
+            _modifiers.append(keys.SHIFT)
+        if key & (sdl2.SDLK_LCTRL | sdl2.SDLK_RCTRL):
+            _modifiers.append(keys.CONTROL)
+        if key & (sdl2.SDLK_LALT | sdl2.SDLK_RALT):
+            _modifiers.append(keys.ALT)
+        if key & (sdl2.SDLK_LGUI | sdl2.SDLK_RGUI):
+            _modifiers.append(keys.META)
+        for mod in _modifiers:
+            if mod not in self._mods:
+                if down:
+                    self._mods.append(mod)
+            elif not down:
+                self._mods.pop(self._mods.index(mod))
+
+# ------------------------------------------------------------------- timer ---
+
+
+# XXX should probably use SDL_Timer (and SDL_INIT_TIMER)
+
+class TimerBackend(BaseTimerBackend):
+
+    def __init__(self, vispy_timer):
+        BaseTimerBackend.__init__(self, vispy_timer)
+        vispy_timer._app._backend._add_timer(self)
+        self._vispy_stop()
+
+    def _vispy_start(self, interval):
+        self._interval = interval
+        self._next_time = time() + self._interval
+
+    def _vispy_stop(self):
+        self._next_time = float('inf')
+
+    def _tick(self):
+        if time() >= self._next_time:
+            self._vispy_timer._timeout()
+            self._next_time = time() + self._interval
diff --git a/vispy/app/backends/_template.py b/vispy/app/backends/_template.py
new file mode 100644
index 0000000..9e21074
--- /dev/null
+++ b/vispy/app/backends/_template.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, 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 division
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import keys
+
+
+# -------------------------------------------------------------------- init ---
+
+# 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,
+}
+
+
+# -------------------------------------------------------------- capability ---
+
+# These are all booleans. Note that they mirror many of the kwargs to
+# the initialization of the Canvas class.
+capability = dict(
+    # if True they mean:
+    title=False,          # can set title on the fly
+    size=False,           # can set size on the fly
+    position=False,       # can set position on the fly
+    show=False,           # can show/hide window XXX ?
+    vsync=False,          # can set window to sync to blank
+    resizable=False,      # can toggle resizability (e.g., no user resizing)
+    decorate=False,       # can toggle decorations
+    fullscreen=False,     # fullscreen window support
+    context=False,        # can share contexts between windows
+    multi_window=False,   # can use multiple windows at once
+    scroll=False,         # scroll-wheel events are supported
+    parent=False,         # can pass native widget backend parent
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+def _set_config(c):
+    """Set gl configuration for template"""
+    raise NotImplementedError
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'template'
+
+
+# ------------------------------------------------------------- application ---
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__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()
+
+
+# ------------------------------------------------------------------ canvas ---
+
+# You can mix this class with the native widget
+class CanvasBackend(BaseCanvasBackend):
+    """Template backend
+
+    Events to emit are shown below. Most backends will probably
+    have one method for each event:
+
+        self._vispy_canvas.events.initialize()
+        self._vispy_canvas.events.resize(size=(w, h))
+        self._vispy_canvas.events.draw(region=None)
+        self._vispy_canvas.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=())
+
+    In most cases, if the window-cross is clicked, a native close-event is
+    generated, which should then call canvas.close(). The Canvas class is
+    responsible for firing the close event and calling
+    backend_canvas._vispy_close, which closes the native widget.
+    If this happens to result in a second close event, canvas.close() gets
+    called again, but Canvas knows it is closing so it stops there.
+
+    If canvas.close() is called (by the user), it calls
+    backend_canvas._vispy_close, which closes the native widget,
+    and we get the same stream of actions as above. This deviation from
+    having events come from the CanvasBackend is necessitated by how
+    different backends handle close events, and the various ways such
+    events can be triggered.
+    """
+
+    def __init__(self, vispy_canvas, *args, **kwargs):
+        # NativeWidgetClass.__init__(self, *args, **kwargs)
+        BaseCanvasBackend.__init__(self, vispy_canvas, SharedContext)
+
+    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_set_fullscreen(self, fullscreen):
+        # Set the current fullscreen state
+        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_fullscreen(self):
+        # Should return the current fullscreen state
+        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
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend):  # Can be mixed with native timer class
+
+    def __init__(self, vispy_timer):
+        BaseTimerBackend.__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/backends/_test.py b/vispy/app/backends/_test.py
new file mode 100644
index 0000000..275d1c7
--- /dev/null
+++ b/vispy/app/backends/_test.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+available = False
+why_not = 'test backend should be skipped'
+testable = False
+which = None
diff --git a/vispy/app/backends/_wx.py b/vispy/app/backends/_wx.py
new file mode 100644
index 0000000..d137c95
--- /dev/null
+++ b/vispy/app/backends/_wx.py
@@ -0,0 +1,425 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+vispy backend for wxPython.
+"""
+
+from __future__ import division
+
+from time import sleep
+import gc
+import warnings
+
+from ..base import (BaseApplicationBackend, BaseCanvasBackend,
+                    BaseTimerBackend, BaseSharedContext)
+from ...util import keys, logger
+from ...util.ptime import time
+
+
+# -------------------------------------------------------------------- init ---
+
+try:
+    # avoid silly locale warning on OSX
+    with warnings.catch_warnings(record=True):
+        import wx
+        from wx import Frame, glcanvas
+
+    # Map native keys to vispy keys
+    KEYMAP = {
+        wx.WXK_SHIFT: keys.SHIFT,
+        wx.WXK_CONTROL: keys.CONTROL,
+        wx.WXK_ALT: keys.ALT,
+        wx.WXK_WINDOWS_MENU: keys.META,
+
+        wx.WXK_LEFT: keys.LEFT,
+        wx.WXK_UP: keys.UP,
+        wx.WXK_RIGHT: keys.RIGHT,
+        wx.WXK_DOWN: keys.DOWN,
+        wx.WXK_PAGEUP: keys.PAGEUP,
+        wx.WXK_PAGEDOWN: keys.PAGEDOWN,
+
+        wx.WXK_INSERT: keys.INSERT,
+        wx.WXK_DELETE: keys.DELETE,
+        wx.WXK_HOME: keys.HOME,
+        wx.WXK_END: keys.END,
+
+        wx.WXK_ESCAPE: keys.ESCAPE,
+        wx.WXK_BACK: keys.BACKSPACE,
+
+        wx.WXK_F1: keys.F1,
+        wx.WXK_F2: keys.F2,
+        wx.WXK_F3: keys.F3,
+        wx.WXK_F4: keys.F4,
+        wx.WXK_F5: keys.F5,
+        wx.WXK_F6: keys.F6,
+        wx.WXK_F7: keys.F7,
+        wx.WXK_F8: keys.F8,
+        wx.WXK_F9: keys.F9,
+        wx.WXK_F10: keys.F10,
+        wx.WXK_F11: keys.F11,
+        wx.WXK_F12: keys.F12,
+
+        wx.WXK_SPACE: keys.SPACE,
+        wx.WXK_RETURN: keys.ENTER,  # == pyglet.window.key.RETURN
+        wx.WXK_NUMPAD_ENTER: keys.ENTER,
+        wx.WXK_TAB: keys.TAB,
+    }
+except Exception as exp:
+    available, testable, why_not, which = False, False, str(exp), None
+
+    class GLCanvas(object):
+        pass
+    Frame = GLCanvas
+else:
+    available, testable, why_not = True, True, None
+    which = 'wxPython ' + str(wx.__version__)
+
+
+# -------------------------------------------------------------- capability ---
+
+capability = dict(  # things that can be set by the backend
+    title=True,
+    size=True,
+    position=True,
+    show=True,
+    vsync=True,
+    resizable=True,
+    decorate=True,
+    fullscreen=True,
+    context=True,
+    multi_window=True,
+    scroll=True,
+    parent=True,
+)
+
+
+# ------------------------------------------------------- set_configuration ---
+
+def _set_config(c):
+    """Set gl configuration"""
+    gl_attribs = [glcanvas.WX_GL_RGBA,
+                  glcanvas.WX_GL_DEPTH_SIZE, c['depth_size'],
+                  glcanvas.WX_GL_STENCIL_SIZE, c['stencil_size'],
+                  glcanvas.WX_GL_MIN_RED, c['red_size'],
+                  glcanvas.WX_GL_MIN_GREEN, c['green_size'],
+                  glcanvas.WX_GL_MIN_BLUE, c['blue_size'],
+                  glcanvas.WX_GL_MIN_ALPHA, c['alpha_size']]
+    gl_attribs += [glcanvas.WX_GL_DOUBLEBUFFER] if c['double_buffer'] else []
+    gl_attribs += [glcanvas.WX_GL_STEREO] if c['stereo'] else []
+    return gl_attribs
+
+
+class SharedContext(BaseSharedContext):
+    _backend = 'pyglet'
+
+
+# ------------------------------------------------------------- application ---
+
+_wx_app = None
+_timers = []
+
+
+class ApplicationBackend(BaseApplicationBackend):
+
+    def __init__(self):
+        BaseApplicationBackend.__init__(self)
+        self._event_loop = wx.EventLoop()
+        wx.EventLoop.SetActive(self._event_loop)
+
+    def _vispy_get_backend_name(self):
+        return 'wx'
+
+    def _vispy_process_events(self):
+        # inpsired by https://github.com/wxWidgets/wxPython/blob/master/
+        #             samples/mainloop/mainloop.py
+        for _ in range(3):  # trial-and-error found this to work (!)
+            while self._event_loop.Pending():
+                self._event_loop.Dispatch()
+            _wx_app.ProcessIdle()
+            sleep(0.01)
+
+    def _vispy_run(self):
+        return _wx_app.MainLoop()
+
+    def _vispy_quit(self):
+        global _wx_app
+        _wx_app.ExitMainLoop()
+
+    def _vispy_get_native_app(self):
+        # Get native app in save way. Taken from guisupport.py
+        global _wx_app
+        _wx_app = wx.GetApp()  # in case the user already has one
+        if _wx_app is None:
+            _wx_app = wx.PySimpleApp()
+        _wx_app.SetExitOnFrameDelete(True)
+        return _wx_app
+
+
+# ------------------------------------------------------------------ canvas ---
+
+def _get_mods(evt):
+    """Helper to extract list of mods from event"""
+    mods = []
+    mods += [keys.CONTROL] if evt.ControlDown() else []
+    mods += [keys.ALT] if evt.AltDown() else []
+    mods += [keys.SHIFT] if evt.ShiftDown() else []
+    mods += [keys.META] if evt.MetaDown() else []
+    return mods
+
+
+def _process_key(evt):
+    """Helper to convert from wx keycode to vispy keycode"""
+    key = evt.GetKeyCode()
+    if key in KEYMAP:
+        return KEYMAP[key], ''
+    if 97 <= key <= 122:
+        key -= 32
+    if key >= 32 and key <= 127:
+        return keys.Key(chr(key)), chr(key)
+    else:
+        return None, None
+
+
+class DummySize(object):
+    def __init__(self, size):
+        self.size = size
+
+    def GetSize(self):
+        return self.size
+
+    def Skip(self):
+        pass
+
+
+class CanvasBackend(Frame, BaseCanvasBackend):
+
+    """ wxPython backend for Canvas abstract class."""
+
+    def __init__(self, **kwargs):
+        BaseCanvasBackend.__init__(self, capability, SharedContext)
+        title, size, position, show, vsync, resize, dec, fs, parent, context, \
+            vispy_canvas = self._process_backend_kwargs(kwargs)
+        if not isinstance(context, (dict, SharedContext)):
+            raise TypeError('context must be a dict or wx SharedContext')
+        style = (wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.CLOSE_BOX |
+                 wx.SYSTEM_MENU | wx.CAPTION | wx.CLIP_CHILDREN)
+        style |= wx.NO_BORDER if not dec else wx.RESIZE_BORDER
+        self._init = False
+        Frame.__init__(self, parent, wx.ID_ANY, title, position, size, style)
+        if not resize:
+            self.SetSizeHints(size[0], size[1], size[0], size[1])
+        if fs is not False:
+            if fs is not True:
+                logger.warning('Cannot specify monitor number for wx '
+                               'fullscreen, using default')
+            self._fullscreen = True
+        else:
+            self._fullscreen = False
+        _wx_app.SetTopWindow(self)
+        if not isinstance(context, SharedContext):
+            self._gl_attribs = _set_config(context)
+        else:
+            self._gl_attribs = context.value[0]
+        self._canvas = glcanvas.GLCanvas(self, wx.ID_ANY, wx.DefaultPosition,
+                                         wx.DefaultSize, 0, 'GLCanvas',
+                                         self._gl_attribs)
+        self._canvas.Raise()
+        self._canvas.SetFocus()
+        self._vispy_set_title(title)
+        if not isinstance(context, SharedContext):
+            self._context = glcanvas.GLContext(self._canvas)
+        else:
+            self._context = context.value[1]
+        self._size = None
+        self.Bind(wx.EVT_SIZE, self.on_resize)
+        self._canvas.Bind(wx.EVT_PAINT, self.on_paint)
+        self._canvas.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
+        self._canvas.Bind(wx.EVT_KEY_UP, self.on_key_up)
+        self._canvas.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse_event)
+        self.Bind(wx.EVT_CLOSE, self.on_close)
+        self._size_init = size
+        self._vispy_set_visible(show)
+
+    def on_resize(self, event):
+        if self._vispy_canvas is None or not self._init:
+            event.Skip()
+            return
+        size = event.GetSize()
+        self._vispy_canvas.events.resize(size=size)
+        self.Refresh()
+        event.Skip()
+
+    def on_paint(self, event):
+        if self._vispy_canvas is None or self._canvas is None:
+            return
+        dc = wx.PaintDC(self)  # needed for wx
+        if not self._init:
+            self._initialize()
+        self._vispy_set_current()
+        self._vispy_canvas.events.draw(region=None)
+        del dc
+        event.Skip()
+
+    def _initialize(self):
+        if self._vispy_canvas is None:
+            return
+        self._init = True
+        self._vispy_set_current()
+        self._vispy_canvas.events.initialize()
+        self.on_resize(DummySize(self._size_init))
+
+    def _vispy_set_current(self):
+        if self._canvas is None:
+            return
+        self._canvas.SetCurrent(self._context)
+
+    @property
+    def _vispy_context(self):
+        """Context to return for sharing"""
+        return SharedContext([self._gl_attribs, self._context])
+
+    def _vispy_warmup(self):
+        etime = time() + 0.3
+        while time() < etime:
+            sleep(0.01)
+            self._vispy_set_current()
+            self._vispy_canvas.app.process_events()
+
+    def _vispy_swap_buffers(self):
+        # Swap front and back buffer
+        if self._canvas is None:
+            return
+        self._vispy_set_current()
+        self._canvas.SwapBuffers()
+
+    def _vispy_set_title(self, title):
+        # Set the window title. Has no effect for widgets
+        self.SetLabel(title)
+
+    def _vispy_set_size(self, w, h):
+        # Set size of the widget or window
+        if not self._init:
+            self._size_init = (w, h)
+        self.SetSizeWH(w, h)
+
+    def _vispy_set_position(self, x, y):
+        # Set positionof the widget or window. May have no effect for widgets
+        self.SetPosition((x, y))
+
+    def _vispy_get_fullscreen(self):
+        return self._fullscreen
+
+    def _vispy_set_fullscreen(self, fullscreen):
+        self._fullscreen = bool(fullscreen)
+        self._vispy_set_visible(True)
+
+    def _vispy_set_visible(self, visible):
+        # Show or hide the window or widget
+        self.Show(visible)
+        if visible:
+            self.ShowFullScreen(self._fullscreen)
+
+    def _vispy_update(self):
+        # Invoke a redraw
+        self.Refresh()
+
+    def _vispy_close(self):
+        if self._vispy_canvas is None or self._canvas is None:
+            return
+        # Force the window or widget to shut down
+        canvas = self._canvas
+        self._canvas = None
+        self._context = None  # let RC destroy this in case it's shared
+        canvas.Close()
+        canvas.Destroy()
+        self.Close()
+        self.Destroy()
+        gc.collect()  # ensure context gets destroyed if it should be
+
+    def _vispy_get_size(self):
+        if self._canvas is None or self._vispy_canvas is None:
+            return
+        w, h = self.GetClientSize()
+        return w, h
+
+    def _vispy_get_position(self):
+        if self._vispy_canvas is None or self._canvas is None:
+            return
+        x, y = self.GetPosition()
+        return x, y
+
+    def on_close(self, evt):
+        if self._vispy_canvas is None:
+            return
+        self._vispy_canvas.close()
+
+    def on_mouse_event(self, evt):
+        if self._vispy_canvas is None or self._canvas is None:
+            return
+        pos = (evt.GetX(), evt.GetY())
+        mods = _get_mods(evt)
+        if evt.GetWheelRotation() != 0:
+            delta = (0., float(evt.GetWheelRotation()))
+            self._vispy_canvas.events.mouse_wheel(delta=delta, pos=pos,
+                                                  modifiers=mods)
+        elif evt.Moving() or evt.Dragging():  # mouse move event
+            self._vispy_mouse_move(pos=pos, modifiers=mods)
+        elif evt.ButtonDown():
+            if evt.LeftDown():
+                button = 0
+            elif evt.MiddleDown():
+                button = 1
+            elif evt.RightDown():
+                button = 2
+            else:
+                evt.Skip()
+            self._vispy_mouse_press(pos=pos, button=button, modifiers=mods)
+        elif evt.ButtonUp():
+            if evt.LeftUp():
+                button = 0
+            elif evt.MiddleUp():
+                button = 1
+            elif evt.RightUp():
+                button = 2
+            else:
+                evt.Skip()
+            self._vispy_mouse_release(pos=pos, button=button, modifiers=mods)
+        evt.Skip()
+
+    def on_key_down(self, evt):
+        if self._vispy_canvas is None or self._canvas is None:
+            return
+        key, text = _process_key(evt)
+        self._vispy_canvas.events.key_press(key=key, text=text,
+                                            modifiers=_get_mods(evt))
+
+    def on_key_up(self, evt):
+        if self._vispy_canvas is None or self._canvas is None:
+            return
+        key, text = _process_key(evt)
+        self._vispy_canvas.events.key_release(key=key, text=text,
+                                              modifiers=_get_mods(evt))
+
+
+# ------------------------------------------------------------------- timer ---
+
+class TimerBackend(BaseTimerBackend):
+
+    def __init__(self, vispy_timer):
+        BaseTimerBackend.__init__(self, vispy_timer)
+        assert _wx_app is not None
+        parent = _wx_app.GetTopWindow()  # assume it's the parent window
+        self._timer = wx.Timer(parent, -1)
+        parent.Bind(wx.EVT_TIMER, self._vispy_timeout, self._timer)
+
+    def _vispy_start(self, interval):
+        self._timer.Start(interval * 1000., False)
+
+    def _vispy_stop(self):
+        self._timer.Stop()
+
+    def _vispy_timeout(self, evt):
+        self._vispy_timer._timeout()
+        evt.Skip()
diff --git a/vispy/app/backends/glut.py b/vispy/app/backends/glut.py
deleted file mode 100644
index 0a7bf1b..0000000
--- a/vispy/app/backends/glut.py
+++ /dev/null
@@ -1,432 +0,0 @@
-# -*- 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
deleted file mode 100644
index 71bbbbd..0000000
--- a/vispy/app/backends/pyglet.py
+++ /dev/null
@@ -1,343 +0,0 @@
-# -*- 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
deleted file mode 100644
index 6e2a1d4..0000000
--- a/vispy/app/backends/qt.py
+++ /dev/null
@@ -1,322 +0,0 @@
-# -*- 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
deleted file mode 100644
index 4c5eac9..0000000
--- a/vispy/app/backends/template.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- 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/base.py b/vispy/app/base.py
new file mode 100644
index 0000000..2fe6d5b
--- /dev/null
+++ b/vispy/app/base.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+
+from inspect import getargspec
+from copy import deepcopy
+
+from ._config import get_default_config
+
+
+class BaseApplicationBackend(object):
+    """BaseApplicationBackend()
+
+    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
+
+
+class BaseCanvasBackend(object):
+    """BaseCanvasBackend(vispy_canvas, capability, context_type)
+
+    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',
+    'draw', 'mouse_press', 'mouse_release', 'mouse_move',
+    'mouse_wheel', 'key_press', 'key_release'. When a backend detects
+    that the canvas should be closed, the backend should call
+    'self._vispy_canvas.close', because the close event is handled within
+    the canvas itself.
+    """
+
+    def __init__(self, capability, context_type):
+        # 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,
+        }
+        self._vispy_capability = capability
+        self._vispy_context_type = context_type
+
+    def _process_backend_kwargs(self, kwargs):
+        """Removes vispy-specific kwargs for CanvasBackend"""
+        # these are the output arguments
+        keys = ['title', 'size', 'position', 'show', 'vsync', 'resizable',
+                'decorate', 'fullscreen', 'parent']
+        from .canvas import Canvas
+        outs = []
+        spec = getargspec(Canvas.__init__)
+        for key in keys:
+            default = spec.defaults[spec.args.index(key) - 1]
+            out = kwargs.get(key, default)
+            if out != default and self._vispy_capability[key] is False:
+                raise RuntimeError('Cannot set property %s using this '
+                                   'backend' % key)
+            outs.append(out)
+
+        # now we add context, which we have to treat slightly differently
+        default_config = get_default_config()
+        context = kwargs.get('context', default_config)
+        can_share = self._vispy_capability['context']
+        # check the type
+        if isinstance(context, self._vispy_context_type):
+            if not can_share:
+                raise RuntimeError('Cannot share context with this backend')
+        elif isinstance(context, dict):
+            # first, fill in context with any missing entries
+            context = deepcopy(context)
+            for key, val in default_config.items():
+                context[key] = context.get(key, default_config[key])
+            # now make sure everything is of the proper type
+            for key, val in context.items():
+                if key not in default_config:
+                    raise KeyError('context has unknown key %s' % key)
+                needed = type(default_config[key])
+                if not isinstance(val, needed):
+                    raise TypeError('context["%s"] is of incorrect type (got '
+                                    '%s need %s)' % (key, type(val), needed))
+        else:
+            raise TypeError('context must be a dict or SharedContext from '
+                            'a Canvas with the same backend, not %s'
+                            % type(context))
+        outs.append(context)
+        outs.append(kwargs.get('vispy_canvas', None))
+        return outs
+
+    def _vispy_init(self):
+        # For any __init__-like actions that must occur *after*
+        # self._vispy_canvas._backend is not None
+
+        # Most backends won't need this. However, there are exceptions.
+        # e.g., pyqt4 with show=True, "show" can't be done until this property
+        # exists because it might call on_draw which might in turn call
+        # canvas.size... which relies on canvas._backend being set.
+        pass
+
+    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_set_fullscreen(self, fullscreen):
+        # Set fullscreen mode
+        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_fullscreen(self):
+        # Should return bool for fullscreen status
+        raise NotImplementedError()
+
+    def _vispy_get_geometry(self):
+        # Should return widget (x, y, w, h)
+        x, y = self._vispy_get_position()
+        w, h = self._vispy_get_size()
+        return x, y, w, h
+
+    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()
+        else:
+            kwds['button'] = self._vispy_mouse_data['press_event'].button
+
+        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
+
+
+class BaseTimerBackend(object):
+    """BaseTimerBackend(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 NotImplementedError
+
+    def _vispy_stop(self):
+        raise NotImplementedError
+
+    def _vispy_get_native_timer(self):
+        # Should return the native timer object
+        # Most backends would not need to implement this
+        return self
+
+
+class BaseSharedContext(object):
+    """An object encapsulating data necessary for a shared OpenGL context
+
+    The data are backend dependent."""
+    def __init__(self, value):
+        self._value = value
+
+    @property
+    def value(self):
+        return self._value
+
+    def __repr__(self):
+        return ("<SharedContext for %s backend" % self._backend)
diff --git a/vispy/app/canvas.py b/vispy/app/canvas.py
index 02a2f00..f4c1dd3 100644
--- a/vispy/app/canvas.py
+++ b/vispy/app/canvas.py
@@ -1,463 +1,521 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
-from __future__ import print_function, division, absolute_import
+from __future__ import division, print_function
 
-from vispy.util.event import EmitterGroup, Event
-import vispy
+import sys
 import numpy as np
+from time import sleep
+
+from ..util.event import EmitterGroup, Event, WarningEmitter
+from ..util.ptime import time
+from ..ext.six import string_types
+from . import Application, use_app
+from ._config import get_default_config
 
 # todo: add functions for asking about current mouse/keyboard state
 # todo: add hover enter/exit events
 # todo: add focus events
 
 
+def _gloo_initialize(event):
+    from ..gloo import gl_initialize
+    gl_initialize()
+
 
 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.
-    
+    """Representation of a GUI element with an OpenGL context
+
     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
+    initialize, resize, draw, mouse_press, mouse_release, mouse_move,
+    mouse_wheel, key_press, key_release, stylus, touch, close
+
+    Parameters
+    ----------
+    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)
+    size : (width, height)
         The size of the window.
-    position :: (x, y)
+    position : (x, y)
         The position of the window in screen coordinates.
-    show :: bool
+    show : bool
         Whether to show the widget immediately. Default False.
-    autoswap :: bool
-        Whether to swap the buffers automatically after a paint event.
-        Default True.
-    
+    autoswap : bool
+        Whether to swap the buffers automatically after a draw event.
+        Default True. If True, the ``swap_buffers`` Canvas method will
+        be called last (by default) by the ``canvas.draw`` event handler.
+    app : Application | str
+        Give vispy Application instance to use as a backend.
+        (vispy.app is used by default.) If str, then an application
+        using the chosen backend (e.g., 'pyglet') will be created.
+        Note the canvas application can be accessed at ``canvas.app``.
+    create_native : bool
+        Whether to create the widget immediately. Default True.
+    init_gloo : bool
+        Initialize standard values in gloo (e.g., ``GL_POINT_SPRITE``).
+    vsync : bool
+        Enable vertical synchronization.
+    resizable : bool
+        Allow the window to be resized.
+    decorate : bool
+        Decorate the window.
+    fullscreen : bool | int
+        If False, windowed mode is used (default). If True, the default
+        monitor is used. If int, the given monitor number is used.
+    context : dict | instance SharedContext | None
+        OpenGL configuration to use when creating the context for the canvas,
+        or a context to share. If None, ``vispy.app.get_default_config`` will
+        be used to set the OpenGL context parameters. Alternatively, the
+        ``canvas.context`` property from an existing canvas (using the
+        same backend) will return a ``SharedContext`` that can be used,
+        thereby sharing the existing context.
+    keys : str | dict | None
+        Default key mapping to use. If 'interactive', escape and F11 will
+        close the canvas and toggle full-screen mode, respectively.
+        If dict, maps keys to functions. If dict values are strings,
+        they are assumed to be ``Canvas`` methods, otherwise they should
+        be callable.
+    parent : widget-object
+        The parent widget if this makes sense for the used backend.
     """
-    
-    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
+
+    def __init__(self, title='Vispy canvas', size=(800, 600), position=None,
+                 show=False, autoswap=True, app=None, create_native=True,
+                 init_gloo=True, vsync=False, resizable=True, decorate=True,
+                 fullscreen=False, context=None, keys=None, parent=None):
+
+        size = [int(s) for s in size]
+        if len(size) != 2:
+            raise ValueError('size must be a 2-element list')
+        title = str(title)
+        if not isinstance(fullscreen, (bool, int)):
+            raise TypeError('fullscreen must be bool or int')
+        if context is None:
+            context = get_default_config()
+
+        # Initialize some values
+        self._autoswap = autoswap
+        self._title = title
+        self._frame_count = 0
+        self._fps = 0
+        self._basetime = time()
+        self._fps_callback = None
         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'))
+        self._closed = False
+
+        # Create events
+        self.events = EmitterGroup(source=self,
+                                   initialize=Event,
+                                   resize=ResizeEvent,
+                                   draw=DrawEvent,
+                                   mouse_press=MouseEvent,
+                                   mouse_release=MouseEvent,
+                                   mouse_move=MouseEvent,
+                                   mouse_wheel=MouseEvent,
+                                   key_press=KeyEvent,
+                                   key_release=KeyEvent,
+                                   stylus=Event,
+                                   touch=Event,
+                                   close=Event)
+
+        # Deprecated paint emitter
+        emitter = WarningEmitter('Canvas.events.paint and Canvas.on_paint are '
+                                 'deprecated; use Canvas.events.draw and '
+                                 'Canvas.on_draw instead.',
+                                 source=self, type='draw',
+                                 event_class=DrawEvent)
+        self.events.add(paint=emitter)
+        self.events.draw.connect(self.events.paint)
+
+        # Initialize gloo settings
+        if init_gloo:
+            self.events.initialize.connect(_gloo_initialize,
+                                           ref='gloo_initialize')
+
+        # store arguments that get set on Canvas init
+        kwargs = dict(title=title, size=size, position=position, show=show,
+                      vsync=vsync, resizable=resizable, decorate=decorate,
+                      fullscreen=fullscreen, context=context, parent=parent,
+                      vispy_canvas=self)
+        self._backend_kwargs = kwargs
+
+        # Get app instance
+        if app is None:
+            self._app = use_app()
+        elif isinstance(app, Application):
+            self._app = app
+        elif isinstance(app, string_types):
+            self._app = Application(app)
         else:
+            raise ValueError('Invalid value for app %r' % app)
+
+        # Deal with special keys
+        self._set_keys(keys)
+
+        # Create widget now (always do this *last*, after all err checks)
+        if create_native:
             self.create_native()
-    
-    
+
+        if '--vispy-fps' in sys.argv:
+            self.measure_fps()
+
     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
-    
+        if self._backend is not None:
+            return
+        # Make sure that the app is active
+        assert self._app.native
+        # Instantiate the backend with the right class
+        be = self._app.backend_module.CanvasBackend(**self._backend_kwargs)
+        self._set_backend(be)
+
     def _set_backend(self, backend):
+        """ Set backend<->canvas references and autoswap
+        """
+        # NOTE: Do *not* combine this with create_native above, since
+        # this private function is used to embed Qt widgets
+        assert backend is not None  # should never happen
         self._backend = backend
-        if backend is not None:
-            backend._vispy_canvas = self
+        if self._autoswap:
+            # append to the end
+            self.events.draw.connect((self, 'swap_buffers'),
+                                     ref=True, position='last')
+        self._backend._vispy_canvas = self  # it's okay to set this again
+        self._backend._vispy_init()
+
+    def _set_keys(self, keys):
+        if keys is not None:
+            if isinstance(keys, string_types):
+                if keys != 'interactive':
+                    raise ValueError('keys, if string, must be "interactive", '
+                                     'not %s' % (keys,))
+
+                def toggle_fs():
+                    self.fullscreen = not self.fullscreen
+                keys = dict(escape='close', F11=toggle_fs)
         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()
-        
-    
+            keys = {}
+        if not isinstance(keys, dict):
+            raise TypeError('keys must be a dict, str, or None')
+        if len(keys) > 0:
+            # ensure all are callable
+            for key, val in keys.items():
+                if isinstance(val, string_types):
+                    new_val = getattr(self, val, None)
+                    if new_val is None:
+                        raise ValueError('value %s is not an attribute of '
+                                         'Canvas' % val)
+                    val = new_val
+                if not hasattr(val, '__call__'):
+                    raise TypeError('Entry for key %s is not callable' % key)
+                # convert to lower-case representation
+                keys.pop(key)
+                keys[key.lower()] = val
+            self._keys_check = keys
+
+            def keys_check(event):
+                use_name = event.key.name.lower()
+                if use_name in self._keys_check:
+                    self._keys_check[use_name]()
+            self.events.key_press.connect(keys_check, ref=True)
+
+    @property
+    def context(self):
+        """ The OpenGL context of the native widget
+        """
+        return self._backend._vispy_context
+
     @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').
-        
+        should be on_X, with X the name of the event (e.g. 'on_draw').
+
         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_"')
+            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)
+            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])
-    
-    
+        return self._backend._vispy_set_size(size[0], size[1])
+
+    @property
+    def fullscreen(self):
+        return self._backend._vispy_get_fullscreen()
+
+    @fullscreen.setter
+    def fullscreen(self, fullscreen):
+        return self._backend._vispy_set_fullscreen(fullscreen)
+
     # ------------------------------------------------------------ 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])
+        assert len(position) == 2
+        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):
+    # ----------------------------------------------------------------- fps ---
+    @property
+    def fps(self):
+        """ The fps of canvas/window, measured as the rate that events.draw
+        is emitted. """
+        return self._fps
+
+    def swap_buffers(self, event=None):
         """ 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 update(self, event=None):
+        """ Inform the backend that the Canvas needs to be redrawn
+        
+        This method accepts an optional ``event`` argument so it can be used
+        as an event handler (the argument is ignored). 
+        """
+        if self._backend is not None:
+            return self._backend._vispy_update()
+        else:
+            return
 
-    
     def close(self):
-        """ Close the canvas """
+        """ Close the canvas
+
+        Note: This will usually destroy the GL context. For Qt, the context
+        (and widget) will be destroyed only if the widget is top-level.
+        To avoid having the widget destroyed (more like standard Qt
+        behavior), consider making the widget a sub-widget.
+        """
+        if self._backend is not None and not self._closed:
+            self._closed = True
+            self.events.close()
+            self._backend._vispy_close()
+
+    def _update_fps(self, event):
+        """ Updates the fps after every window and resets the basetime
+        and frame count to current time and 0, respectively
+        """
+        self._frame_count += 1
+        diff = time() - self._basetime
+        if (diff > self._fps_window):
+            self._fps = self._frame_count / diff
+            self._basetime = time()
+            self._frame_count = 0
+            self._fps_callback(self.fps)
+
+    def measure_fps(self, window=1, callback='%1.1f FPS'):
+        """Measure the current FPS
+
+        Sets the update window, connects the draw event to update_fps
+        and sets the callback function. 
+
+        Parameters
+        ----------
+        window : float
+            The time-window (in seconds) to calculate FPS. Default 1.0.
+        callback : function | str
+            The function to call with the float FPS value, or the string
+            to be formatted with the fps value and then printed. The
+            default is '%1.1f FPS'. If callback evaluates to False, the
+            FPS measurement is stopped.
+        """
+        # Connect update_fps function to draw
+        self.events.draw.disconnect(self._update_fps)
+        if callback:
+            if isinstance(callback, string_types):
+                callback_str = callback  # because callback gets overwritten
+                callback = lambda x: print(callback_str % x)
+            self._fps_window = window
+            self.events.draw.connect(self._update_fps)
+            self._fps_callback = callback
+        else:
+            self._fps_callback = None
+
+    # ---------------------------------------------------------------- misc ---
+    def __repr__(self):
+        return ('<Vispy canvas (%s backend) at %s>'
+                % (self.app.backend_name, hex(id(self))))
 
-        self._backend._vispy_close()
-    
-    
-    #def mouse_event(self, event):
+    def __enter__(self):
+        self.show()
+        self._backend._vispy_warmup()
+        return self
+
+    def __exit__(self, type, value, traceback):
+        # ensure all GL calls are complete
+        if not self._closed:
+            from ..gloo import gl
+            self._backend._vispy_set_current()
+            gl.glFinish()
+            self.close()
+        sleep(0.1)  # ensure window is really closed/destroyed
+
+    # 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):
+        # 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), ... ]
+
+        # Event properties:
+        #     event.touches
+        #     [ (x,y,pressure), ... ]
         #"""
-        
-    #def stylus_event(self, event):
+
+    # 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
-            
+
+        # 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):
+    # 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
+
+        # Event properties:
+        #     event.size  (w,h)
         #"""
-    
-
-
-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
+    # def draw_event(self, event):
+        #"""Called when all or part of the Canvas needs to be redrawn.
+
+        # Event properties:
+        #     event.region  (x,y,w,h) region of Canvas requiring redraw
+        #"""
 
 
+# Event subclasses specific to the Canvas
 class MouseEvent(Event):
-    """ Class describing mouse events.
-    
+
+    """Mouse event class
+
     Note that each event object has an attribute for each of the input
     arguments listed below.
-    
-    Input arguments
-    ---------------
+
+    Parameters
+    ----------
     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
+    button : int | None
         The button that generated this event (can be None).
-        Left=1, right=2, middle=3.
+        Left=1, right=2, middle=3. During a mouse drag, this
+        will return the button that started the drag (same thing as
+        ``event.press_event.button``).
     buttons : [int, ...]
-        The list of buttons depressed during this event. 
+        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 
+        The amount of scrolling in horizontal and vertical direction. One
         "tick" corresponds to a delta of 1.0.
-    press_event : MouseEvent 
+    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. 
+        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, 
+
+    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._pos = (0, 0) if (pos is None) else (pos[0], pos[1])
+        self._button = int(button) if (button is not None) else None
         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._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
@@ -482,48 +540,49 @@ class MouseEvent(Event):
 
     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:
+            # mouse_press events can only be the start of a trail
+            if event is None or event.type == 'mouse_press':
                 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. 
-        
+        """ 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.
-    
+
+    """Key event class
+
     Note that each event object has an attribute for each of the input
     arguments listed below.
-    
-    Input arguments
-    ---------------
+
+    Parameters
+    ----------
     type : str
        String indicating the event type (e.g. mouse_press, key_release)
     key : vispy.keys.Key instance
@@ -538,35 +597,35 @@ class KeyEvent(Event):
     **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 () )
-    
+        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.
-    
+
+    """ Resize event class
+
     Note that each event object has an attribute for each of the input
     arguments listed below.
-    
-    Input arguments
-    ---------------
+
+    Parameters
+    ----------
     type : str
        String indicating the event type (e.g. mouse_press, key_release)
     size : (int, int)
@@ -576,41 +635,43 @@ class ResizeEvent(Event):
     **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 
+class DrawEvent(Event):
+
+    """ Draw event class
+
+    This type of event is sent to Canvas.events.draw when a redraw
     is required.
-    
+
     Note that each event object has an attribute for each of the input
     arguments listed below.
-    
-    Input arguments
-    ---------------
+
+    Parameters
+    ----------
     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.
+        The region of the canvas which needs to be redrawn (x, y, w, h).
+        If None, the entire canvas must be redrawn.
     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 
+
+    @property
     def region(self):
         return self._region
diff --git a/vispy/shaders/__init__.py b/vispy/app/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/app/tests/__init__.py
diff --git a/vispy/app/tests/qt-designer.ui b/vispy/app/tests/qt-designer.ui
index c84cae6..5e4d3a4 100644
--- a/vispy/app/tests/qt-designer.ui
+++ b/vispy/app/tests/qt-designer.ui
@@ -49,7 +49,7 @@
   <customwidget>
    <class>CanvasBackend</class>
    <extends>QWidget</extends>
-   <header>vispy.app.backends.qt</header>
+   <header>vispy.app.backends._qt</header>
    <container>1</container>
   </customwidget>
  </customwidgets>
diff --git a/vispy/app/tests/test_app.py b/vispy/app/tests/test_app.py
new file mode 100644
index 0000000..74fd6a5
--- /dev/null
+++ b/vispy/app/tests/test_app.py
@@ -0,0 +1,417 @@
+import numpy as np
+import sys
+import os
+from collections import namedtuple
+from time import sleep
+
+from numpy.testing import assert_array_equal
+from nose.tools import assert_equal, assert_true, assert_raises
+
+from vispy.app import use_app, Canvas, Timer, MouseEvent, KeyEvent
+from vispy.app.base import BaseApplicationBackend
+from vispy.testing import requires_application, SkipTest, assert_is, assert_in
+from vispy.util import keys, use_log_level
+
+from vispy.gloo.program import (Program, VertexBuffer, IndexBuffer)
+from vispy.gloo.shader import VertexShader, FragmentShader
+from vispy.gloo.util import _screenshot
+from vispy.gloo import gl
+
+gl.use_gl('desktop debug')
+
+
+def on_nonexist(self, *args):
+    return
+
+
+def on_mouse_move(self, *args):
+    return
+
+
+def _on_mouse_move(self, *args):
+    return
+
+
+def _test_callbacks(canvas):
+    """Tests input capabilities, triaging based on backend"""
+    backend_name = canvas._app.backend_name
+    backend = canvas._backend
+    if backend_name.lower() == 'pyglet':
+        # Test Pyglet callbacks can take reasonable args
+        backend.on_resize(100, 100)
+        backend.our_draw_func()
+        backend.on_mouse_press(10, 10, 1)
+        backend.on_mouse_release(10, 11, 1)
+        backend.on_mouse_motion(10, 12, 0, 1)
+        backend.on_mouse_drag(10, 13, 0, 1, 1, 0)
+        backend.on_mouse_scroll(10, 13, 1, 1)
+        backend.on_key_press(10, 0)
+        backend.on_key_release(10, 0)
+        backend.on_text('foo')
+    elif backend_name.lower() == 'glfw':
+        # Test GLFW callbacks can take reasonable args
+        _id = backend._id
+        backend._on_draw(_id)
+        backend._on_resize(_id, 100, 100)
+        backend._on_key_press(_id, 50, 50, 1, 0)
+        backend._on_mouse_button(_id, 1, 1, 0)
+        backend._on_mouse_scroll(_id, 1, 0)
+        backend._on_mouse_motion(_id, 10, 10)
+        backend._on_close(_id)
+    elif 'qt' in backend_name.lower():
+        # constructing fake Qt events is too hard :(
+        pass
+    elif 'sdl2' in backend_name.lower():
+        event = namedtuple('event', ['type', 'window', 'motion', 'button',
+                                     'wheel', 'key'])
+        event.type = 512  # WINDOWEVENT
+        event.window = namedtuple('window', ['event', 'data1', 'data2'])
+        event.motion = namedtuple('motion', ['x', 'y'])
+        event.button = namedtuple('button', ['x', 'y', 'button'])
+        event.wheel = namedtuple('wheel', ['x', 'y'])
+        event.key = namedtuple('key', ['keysym'])
+        event.key.keysym = namedtuple('keysym', ['mod', 'sym'])
+
+        event.window.event = 5  # WINDOWEVENT_RESIZED
+        event.window.data1 = 10
+        event.window.data2 = 20
+        backend._on_event(event)
+
+        event.type = 1024  # SDL_MOUSEMOTION
+        event.motion.x, event.motion.y = 1, 1
+        backend._on_event(event)
+
+        event.type = 1025  # MOUSEBUTTONDOWN
+        event.button.x, event.button.y, event.button.button = 1, 1, 1
+        backend._on_event(event)
+        event.type = 1026  # MOUSEBUTTONUP
+        backend._on_event(event)
+
+        event.type = 1027  # sdl2.SDL_MOUSEWHEEL
+        event.wheel.x, event.wheel.y = 0, 1
+        backend._on_event(event)
+
+        event.type = 768  # SDL_KEYDOWN
+        event.key.keysym.mod = 1073742049  # SLDK_LSHIFT
+        event.key.keysym.sym = 1073741906  # SDLK_UP
+        backend._on_event(event)
+        event.type = 769  # SDL_KEYUP
+        backend._on_event(event)
+    elif 'glut' in backend_name.lower():
+        backend.on_mouse_action(0, 0, 0, 0)
+        backend.on_mouse_action(0, 1, 0, 0)
+        backend.on_mouse_action(3, 0, 0, 0)
+        backend.on_draw()
+        backend.on_mouse_motion(1, 1)
+        # Skip keypress tests b/c of glutGetModifiers warning
+        #for key in (100, 'a'):
+        #    backend.on_key_press(key, 0, 0)
+        #    backend.on_key_release(key, 0, 0)
+    elif 'wx' in backend_name.lower():
+        # Constructing fake wx events is too hard
+        pass
+    else:
+        raise ValueError
+
+
+ at requires_application()
+def test_run():
+    """Test app running"""
+    a = use_app()
+    if a.backend_name.lower() == 'glut':
+        raise SkipTest('cannot test running glut')  # knownfail
+    for _ in range(2):
+        with Canvas(size=(100, 100), show=True, title=' run') as c:
+            @c.events.draw.connect
+            def draw(event):
+                print(event)  # test event __repr__
+                c.app.quit()
+            c.update()
+            c.app.run()
+        c.app.quit()  # make sure it doesn't break if a user quits twice
+
+
+ at requires_application()
+def test_capability():
+    """Test application capability enumeration"""
+    non_default_vals = dict(title='foo', size=[100, 100], position=[0, 0],
+                            show=True, decorate=False, resizable=False,
+                            vsync=True)  # context is tested elsewhere
+    good_kwargs = dict()
+    bad_kwargs = dict()
+    with Canvas() as c:
+        for key, val in c._backend._vispy_capability.items():
+            if key in non_default_vals:
+                if val:
+                    good_kwargs[key] = non_default_vals[key]
+                else:
+                    bad_kwargs[key] = non_default_vals[key]
+    # ensure all settable values can be set
+    with Canvas(**good_kwargs):
+        # some of these are hard to test, and the ones that are easy are
+        # tested elsewhere, so let's just make sure it runs here
+        pass
+    # ensure that *any* bad argument gets caught
+    for key, val in bad_kwargs.items():
+        assert_raises(RuntimeError, Canvas, **{key: val})
+
+
+ at requires_application()
+def test_application():
+    """Test application running"""
+    app = use_app()
+    print(app)  # __repr__ without app
+    app.create()
+    wrong = 'glut' if app.backend_name.lower() != 'glut' else 'pyglet'
+    assert_raises(RuntimeError, use_app, wrong)
+    app.process_events()
+    print(app)  # test __repr__
+
+    assert_raises(ValueError, Canvas, keys='foo')
+    assert_raises(TypeError, Canvas, keys=dict(escape=1))
+    assert_raises(ValueError, Canvas, keys=dict(escape='foo'))  # not an attr
+
+    pos = [0, 0] if app.backend_module.capability['position'] else None
+    size = (100, 100)
+    # Use "with" statement so failures don't leave open window
+    # (and test context manager behavior)
+    title = 'default'
+    with Canvas(title=title, size=size, app=app, show=True,
+                position=pos) as canvas:
+        assert_true(canvas.create_native() is None)  # should be done already
+        assert_is(canvas.app, app)
+        assert_true(canvas.native)
+        assert_equal('swap_buffers', canvas.events.draw.callback_refs[-1])
+
+        canvas.measure_fps(0.001)
+        sleep(0.002)
+        canvas.update()
+        app.process_events()
+        assert_true(canvas.fps > 0)
+
+        # Other methods
+        print(canvas)  # __repr__
+        assert_equal(canvas.title, title)
+        canvas.title = 'you'
+        with use_log_level('warning', record=True, print_msg=False) as l:
+            if app.backend_module.capability['position']:
+                # todo: disable more tests based on capability
+                canvas.position = pos
+            canvas.size = size
+        if 'ipynb_vnc' in canvas.app.backend_name.lower():
+            assert_true(len(l) >= 1)
+        else:
+            assert_true(len(l) == 0)
+        canvas.connect(on_mouse_move)
+        assert_raises(ValueError, canvas.connect, _on_mouse_move)
+        if sys.platform != 'darwin':  # XXX knownfail, prob. needs warmup
+            canvas.show(False)
+            canvas.show()
+        app.process_events()
+        assert_raises(ValueError, canvas.connect, on_nonexist)
+        # deprecation of "paint"
+        with use_log_level('info', record=True, print_msg=False) as log:
+            olderr = sys.stderr
+            try:
+                with open(os.devnull, 'w') as fid:
+                    sys.stderr = fid
+
+                    @canvas.events.paint.connect
+                    def fake(event):
+                        pass
+            finally:
+                sys.stderr = olderr
+        assert_equal(len(log), 1)
+        assert_in('deprecated', log[0])
+
+        # screenshots
+        gl.glViewport(0, 0, *size)
+        ss = _screenshot()
+        assert_array_equal(ss.shape, size + (4,))
+        assert_equal(len(canvas._backend._vispy_get_geometry()), 4)
+        if (app.backend_name.lower() != 'glut' and  # XXX knownfail for Almar
+                sys.platform != 'win32'):  # XXX knownfail for windows
+            assert_array_equal(canvas.size, size)
+        assert_equal(len(canvas.position), 2)  # XXX knawnfail, doesn't "take"
+
+        # GLOO: should have an OpenGL context already, so these should work
+        vert = VertexShader("void main (void) {gl_Position = pos;}")
+        frag = FragmentShader("void main (void) {gl_FragColor = pos;}")
+        program = Program(vert, frag)
+        assert_raises(RuntimeError, program.activate)
+
+        vert = VertexShader("uniform vec4 pos;"
+                            "void main (void) {gl_Position = pos;}")
+        frag = FragmentShader("uniform vec4 pos;"
+                              "void main (void) {gl_FragColor = pos;}")
+        program = Program(vert, frag)
+        #uniform = program.uniforms[0]
+        program['pos'] = [1, 2, 3, 4]
+        program.activate()  # should print
+        #uniform.upload(program)
+        program.detach(vert)
+        program.detach(frag)
+        assert_raises(RuntimeError, program.detach, vert)
+        assert_raises(RuntimeError, program.detach, frag)
+
+        vert = VertexShader("attribute vec4 pos;"
+                            "void main (void) {gl_Position = pos;}")
+        frag = FragmentShader("void main (void) {}")
+        program = Program(vert, frag)
+        #attribute = program.attributes[0]
+        program["pos"] = [1, 2, 3, 4]
+        program.activate()
+        #attribute.upload(program)
+        # cannot get element count
+        #assert_raises(RuntimeError, program.draw, 'POINTS')
+
+        # use a real program
+        vert = ("uniform mat4 u_model;"
+                "attribute vec2 a_position; attribute vec4 a_color;"
+                "varying vec4 v_color;"
+                "void main (void) {v_color = a_color;"
+                "gl_Position = u_model * vec4(a_position, 0.0, 1.0);"
+                "v_color = a_color;}")
+        frag = "void main() {gl_FragColor = vec4(0, 0, 0, 1);}"
+        n, p = 250, 50
+        T = np.random.uniform(0, 2 * np.pi, n)
+        position = np.zeros((n, 2), dtype=np.float32)
+        position[:, 0] = np.cos(T)
+        position[:, 1] = np.sin(T)
+        color = np.ones((n, 4), dtype=np.float32) * (1, 1, 1, 1)
+        data = np.zeros(n * p, [('a_position', np.float32, 2),
+                                ('a_color', np.float32, 4)])
+        data['a_position'] = np.repeat(position, p, axis=0)
+        data['a_color'] = np.repeat(color, p, axis=0)
+
+        program = Program(vert, frag)
+        program.bind(VertexBuffer(data))
+        program['u_model'] = np.eye(4, dtype=np.float32)
+        # different codepath if no call to activate()
+        program.draw(gl.GL_POINTS)
+        subset = IndexBuffer(np.arange(10, dtype=np.uint32))
+        program.draw(gl.GL_POINTS, subset)
+
+        # bad programs
+        frag_bad = ("varying vec4 v_colors")  # no semicolon
+        program = Program(vert, frag_bad)
+        assert_raises(RuntimeError, program.activate)
+        frag_bad = None  # no fragment code. no main is not always enough
+        program = Program(vert, frag_bad)
+        assert_raises(ValueError, program.activate)
+
+        # Timer
+        timer = Timer(interval=0.001, connect=on_mouse_move, iterations=2,
+                      start=True, app=app)
+        timer.start()
+        timer.interval = 0.002
+        assert_equal(timer.interval, 0.002)
+        assert_true(timer.running)
+        sleep(.003)
+        assert_true(timer.elapsed >= 0.002)
+        timer.stop()
+        assert_true(not timer.running)
+        assert_true(timer.native)
+        timer.disconnect()
+
+        # test that callbacks take reasonable inputs
+        _test_callbacks(canvas)
+
+        # cleanup
+        canvas.swap_buffers()
+        canvas.update()
+        app.process_events()
+        # put this in even though __exit__ will call it to make sure we don't
+        # have problems calling it multiple times
+        canvas.close()  # done by context
+
+
+ at requires_application()
+def test_fs():
+    """Test fullscreen support"""
+    a = use_app()
+    if not a.backend_module.capability['fullscreen']:
+        return
+    assert_raises(TypeError, Canvas, fullscreen='foo')
+    if (a.backend_name.lower() == 'glfw' or
+            (a.backend_name.lower() == 'sdl2' and sys.platform == 'darwin')):
+        raise SkipTest('Backend takes over screen')
+    with use_log_level('warning', record=True, print_msg=False) as l:
+        with Canvas(fullscreen=False) as c:
+            assert_equal(c.fullscreen, False)
+            c.fullscreen = True
+            assert_equal(c.fullscreen, True)
+    assert_equal(len(l), 0)
+    with use_log_level('warning', record=True, print_msg=False):
+        # some backends print a warning b/c fullscreen can't be specified
+        with Canvas(fullscreen=0) as c:
+            assert_equal(c.fullscreen, True)
+
+
+ at requires_application()
+def test_close_keys():
+    """Test close keys"""
+    c = Canvas(keys='interactive')
+    x = list()
+
+    @c.events.close.connect
+    def closer(event):
+        x.append('done')
+    c.events.key_press(key=keys.ESCAPE, text='', modifiers=[])
+    assert_equal(len(x), 1)  # ensure the close event was sent
+    c.app.process_events()
+
+
+ at requires_application()
+def test_event_order():
+    """Test event order"""
+    x = list()
+
+    class MyCanvas(Canvas):
+        def on_initialize(self, event):
+            x.append('init')
+
+        def on_draw(self, event):
+            sz = True if self.size is not None else False
+            x.append('draw size=%s show=%s' % (sz, show))
+
+        def on_close(self, event):
+            x.append('close')
+
+    for show in (False, True):
+        # clear our storage variable
+        while x:
+            x.pop()
+        with MyCanvas(show=show) as c:
+            c.update()
+            c.app.process_events()
+
+        print(x)
+        assert_true(len(x) >= 3)
+        assert_equal(x[0], 'init')
+        assert_in('draw size=True', x[1])
+        assert_in('draw size=True', x[-2])
+        assert_equal(x[-1], 'close')
+
+
+def test_abstract():
+    """Test app abstract template"""
+    app = BaseApplicationBackend()
+    for fun in (app._vispy_get_backend_name, app._vispy_process_events,
+                app._vispy_run, app._vispy_quit):
+        assert_raises(NotImplementedError, fun)
+
+
+def test_mouse_key_events():
+    """Test mouse and key events"""
+    me = MouseEvent('mouse_press')
+    for fun in (me.pos, me.button, me.buttons, me.modifiers, me.delta,
+                me.press_event, me.last_event, me.is_dragging):
+        fun
+    me.drag_events()
+    me._forget_last_event()
+    me.trail()
+    ke = KeyEvent('key_release')
+    ke.key
+    ke.text
+    ke.modifiers
diff --git a/vispy/app/tests/test_backends.py b/vispy/app/tests/test_backends.py
index b40803c..2bda21f 100644
--- a/vispy/app/tests/test_backends.py
+++ b/vispy/app/tests/test_backends.py
@@ -1,140 +1,152 @@
 """ 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 
+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
+from nose.tools import assert_raises
+from inspect import getargspec
 
 import vispy
 from vispy import keys
+from vispy.testing import requires_application, assert_in
+from vispy.app import use_app, Application
+from vispy.app.backends import _template
+
+
+class DummyApplication(Application):
+    def _use(self, backend_namd):
+        pass
+
+
+def _test_module_properties(_module=None):
+    """Test application module"""
+    if _module is None:
+        app = use_app()
+        _module = app.backend_module
 
-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
+    # Test that the keymap contains all keys supported by vispy.
+    module_fname = _module.__name__.split('.')[-1]
+    if module_fname != '_egl':  # skip keys for EGL
+        keymap = _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
-        
+            assert_in(key, vispy_keys)
+
+    # For Qt backend, we have a common implementation
+    alt_modname = ''
+    if module_fname in ('_pyside', '_pyqt4'):
+        alt_modname = _module.__name__.rsplit('.', 1)[0] + '._qt'
+
+    # Test that all _vispy_x methods are there.
+    exceptions = (
+        '_vispy_init',
+        '_vispy_get_native_canvas',
+        '_vispy_get_native_timer',
+        '_vispy_get_native_app',
+        '_vispy_mouse_move',
+        '_vispy_mouse_press',
+        '_vispy_mouse_release',
+        '_vispy_get_geometry',
+        '_process_backend_kwargs')  # defined in base class
+
+    Klass = _module.CanvasBackend
+    KlassRef = vispy.app.base.BaseCanvasBackend
+    base = KlassRef(None, None)
+    for key in dir(KlassRef):
+        if not key.startswith('__'):
+            method = getattr(Klass, key)
+            if key not in exceptions:
+                print(key)
+                args = [None] * (len(getargspec(method).args) - 1)
+                assert_raises(NotImplementedError, getattr(base, key),
+                              *args)
+                if hasattr(method, '__module__'):
+                    mod_str = method.__module__  # Py3k
+                else:
+                    mod_str = method.im_func.__module__
+                assert_in(mod_str, (_module.__name__, alt_modname),
+                          "Method %s.%s not defined in %s"
+                          % (Klass, key, _module.__name__))
+
+    Klass = _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__'):
+                    # Py3k
+                    assert_in(method.__module__,
+                              (_module.__name__, alt_modname))
+                else:
+                    t = method.im_func.__module__ == _module.__name__
+                    assert t
+
+    Klass = _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__'):
+                    # Py3k
+                    assert_in(method.__module__,
+                              (_module.__name__, alt_modname))
+                else:
+                    t = method.im_func.__module__ == _module.__name__
+                    assert t
+
+    # Test that all events seem to be emitted.
+    # Get text
+    fname = _module.__file__.strip('c')
+    with open(fname, 'rb') as fid:
+        text = fid.read().decode('utf-8')
+
+    canvas = vispy.app.Canvas(create_native=False, app=DummyApplication())
+    # 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', 'paint',
+                  'mouse_move', 'mouse_release', 'close'])
+    if module_fname == '_egl':
+        ignore += ['key_release', 'key_press']
+    eventNames = set(canvas.events._emitters.keys()) - ignore
+
+    if not alt_modname:  # Only check for non-proxy modules
         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
+            assert_in('events.%s' % name, text,
+                      'events.%s does not appear in %s' % (name, fname))
+
+
+def test_template():
+    """Test application module template"""
+    _test_module_properties(_template)
+    assert_raises(NotImplementedError, _template._set_config, dict())
+    a = _template.ApplicationBackend()
+    print(a._vispy_get_backend_name())
+    for method in (a._vispy_process_events, a._vispy_run, a._vispy_quit,
+                   a._vispy_get_native_app):
+        assert_raises(NotImplementedError, method)
+
+    c = _template.CanvasBackend(None)
+    print(c._vispy_get_native_canvas())
+    for method in (c._vispy_set_current, c._vispy_swap_buffers, c._vispy_close,
+                   c._vispy_update, c._vispy_get_size, c._vispy_get_position):
+        assert_raises(NotImplementedError, method)
+    for method in (c._vispy_set_title, c._vispy_set_visible):
+        assert_raises(NotImplementedError, method, 0)
+    for method in (c._vispy_set_size, c._vispy_set_position):
+        assert_raises(NotImplementedError, method, 0, 0)
+
+
+ at requires_application()
+def test_actual():
+    """Test actual application module"""
+    _test_module_properties(None)
diff --git a/vispy/app/tests/test_context.py b/vispy/app/tests/test_context.py
new file mode 100644
index 0000000..7b5fc9c
--- /dev/null
+++ b/vispy/app/tests/test_context.py
@@ -0,0 +1,72 @@
+import os
+import sys
+from nose.tools import assert_equal, assert_raises
+
+from vispy.testing import requires_application, SkipTest
+from vispy.app import Canvas, use_app
+from vispy.gloo import (get_gl_configuration, VertexShader, FragmentShader,
+                        Program, check_error)
+
+
+ at requires_application()
+def test_context_properties():
+    """Test setting context properties"""
+    a = use_app()
+    if a.backend_name.lower() == 'pyglet':
+        return  # cannot set more than once on Pyglet
+    # stereo, double buffer won't work on every sys
+    contexts = [dict(samples=4), dict(stencil_size=8),
+                dict(samples=4, stencil_size=8)]
+    if a.backend_name.lower() != 'glfw':  # glfw *always* double-buffers
+        contexts.append(dict(double_buffer=False, samples=4))
+        contexts.append(dict(double_buffer=False))
+    else:
+        assert_raises(RuntimeError, Canvas, app=a,
+                      context=dict(double_buffer=False))
+    if a.backend_name.lower() == 'sdl2' and os.getenv('TRAVIS') == 'true':
+        raise SkipTest('Travis SDL cannot set context')
+    for context in contexts:
+        n_items = len(context)
+        with Canvas(context=context):
+            if os.getenv('TRAVIS', 'false') == 'true':
+                # Travis cannot handle obtaining these values
+                props = context
+            else:
+                props = get_gl_configuration()
+            assert_equal(len(context), n_items)
+            for key, val in context.items():
+                # XXX knownfail for windows samples, and wx (all platforms)
+                if key == 'samples':
+                    iswx = a.backend_name.lower() == 'wx'
+                    if not (sys.platform.startswith('win') or iswx):
+                        assert_equal(val, props[key], key)
+    assert_raises(TypeError, Canvas, context='foo')
+    assert_raises(KeyError, Canvas, context=dict(foo=True))
+    assert_raises(TypeError, Canvas, context=dict(double_buffer='foo'))
+
+
+ at requires_application()
+def test_context_sharing():
+    """Test context sharing"""
+    with Canvas() as c1:
+        vert = VertexShader("uniform vec4 pos;"
+                            "void main (void) {gl_Position = pos;}")
+        frag = FragmentShader("uniform vec4 pos;"
+                              "void main (void) {gl_FragColor = pos;}")
+        program = Program(vert, frag)
+        program['pos'] = [1, 2, 3, 4]
+        program.activate()  # should print
+
+        def check():
+            program.activate()
+            check_error()
+
+        with Canvas() as c:
+            # pyglet always shares
+            if 'pyglet' not in c.app.backend_name.lower():
+                assert_raises(RuntimeError, check)
+        if c1.app.backend_name.lower() in ('glut',):
+            assert_raises(RuntimeError, Canvas, context=c1.context)
+        else:
+            with Canvas(context=c1.context):
+                check()
diff --git a/vispy/app/tests/test_qt.py b/vispy/app/tests/test_qt.py
index 3ec663a..0d74168 100644
--- a/vispy/app/tests/test_qt.py
+++ b/vispy/app/tests/test_qt.py
@@ -3,51 +3,46 @@
 
 # This is a strange test: vispy does not need designer or uic stuff to run!
 
+from os import path as op
+import warnings
 
-try:
-    from PyQt4 import QtCore, QtGui, QtOpenGL, uic
-    test_uic = True
-except ImportError:
-    from PySide import QtCore, QtGui, QtOpenGL
-    test_uic = False
+from vispy.app import Canvas, use_app
+from vispy.testing import requires_application, SkipTest
+from vispy.gloo import gl
 
 
-import OpenGL.GL as gl
-from vispy.app import Canvas
-import os
-
-
-app = QtGui.QApplication([])
-
+ at requires_application('pyqt4', has=['uic'])
 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):  
+    app = use_app()
+    if 'pyqt4' not in app.backend_name.lower():
+        raise SkipTest('Not using PyQt4 backend')  # wrong backend
+    from PyQt4 import uic
+    fname = op.join(op.dirname(__file__), 'qt-designer.ui')
+    with warnings.catch_warnings(record=True):  # pyqt4 deprecation warning
+        WindowTemplate, TemplateBaseClass = uic.loadUiType(fname)
+    app.create()  # make sure we have an app, or the init will fail
+
+    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()
+    win = MainWindow()
+    try:
+        win.show()
+        canvas = Canvas(create_native=False)
+        canvas._set_backend(win.ui.canvas)
+        canvas.create_native()
+
+        @canvas.events.draw.connect
+        def on_draw(ev):
+            gl.glClearColor(0.0, 0.0, 0.0, 0.0)
+            gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+            canvas.swap_buffers()
+    finally:
+        win.close()
diff --git a/vispy/app/tests/test_simultaneous.py b/vispy/app/tests/test_simultaneous.py
new file mode 100644
index 0000000..650c1b3
--- /dev/null
+++ b/vispy/app/tests/test_simultaneous.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+import numpy as np
+from numpy.testing import assert_allclose
+from nose.tools import assert_true
+from time import sleep
+
+from vispy.app import use_app, Canvas, Timer
+from vispy.testing import requires_application, SkipTest
+from vispy.util.ptime import time
+from vispy.gloo import gl
+from vispy.gloo.util import _screenshot
+
+_win_size = (200, 50)
+
+
+def _update_process_check(canvas, val, draw=True):
+    """Update, process, and check result"""
+    if draw:
+        canvas.update()
+        canvas.app.process_events()
+        canvas.app.process_events()
+        sleep(0.03)  # give it time to swap (Qt?)
+    canvas._backend._vispy_set_current()
+    print('           check %s' % val)
+    # check screenshot to see if it's all one color
+    ss = _screenshot()
+    try:
+        assert_allclose(ss.shape[:2], _win_size[::-1])
+    except Exception:
+        print('!!!!!!!!!! FAIL  bad size %s' % list(ss.shape[:2]))
+        raise
+    goal = val * np.ones(ss.shape)
+    try:
+        # Get rid of the alpha value before testing
+        # It can be off by 1 due to rounding
+        assert_allclose(ss[:, :, :3], goal[:, :, :3], atol=1)
+    except Exception:
+        print('!!!!!!!!!! FAIL  %s' % np.unique(ss))
+        raise
+
+
+ at requires_application()
+def test_multiple_canvases():
+    """Testing multiple canvases"""
+    n_check = 3
+    app = use_app()
+    if app.backend_name.lower() == 'glut':
+        raise SkipTest('glut cannot use multiple canvases')
+    with Canvas(app=app, size=_win_size, title='same_0') as c0:
+        with Canvas(app=app, size=_win_size, title='same_1') as c1:
+            ct = [0, 0]
+
+            @c0.events.draw.connect
+            def draw0(event):
+                ct[0] += 1
+                c0.update()
+
+            @c1.events.draw.connect  # noqa, analysis:ignore
+            def draw1(event):
+                ct[1] += 1
+                c1.update()
+
+            c0.show()  # ensure visible
+            c1.show()
+            c0.update()  # force first draw
+            c1.update()
+
+            timeout = time() + 2.0
+            while (ct[0] < n_check or ct[1] < n_check) and time() < timeout:
+                app.process_events()
+            print((ct, n_check))
+            assert_true(n_check <= ct[0] <= n_check + 1)
+            assert_true(n_check <= ct[1] <= n_check + 1)
+
+            # check timer
+            global timer_ran
+            timer_ran = False
+
+            def on_timer(_):
+                global timer_ran
+                timer_ran = True
+            timeout = time() + 2.0
+            Timer(0.1, app=app, connect=on_timer, iterations=1,
+                  start=True)
+            while not timer_ran and time() < timeout:
+                app.process_events()
+            assert_true(timer_ran)
+
+    if app.backend_name.lower() == 'wx':
+        raise SkipTest('wx fails test #2')  # XXX TODO Fix this
+
+    kwargs = dict(app=app, autoswap=False, size=_win_size,
+                  show=True)
+    with Canvas(title='0', **kwargs) as c0:
+        with Canvas(title='1', **kwargs) as c1:
+            bgcolors = [None] * 2
+
+            @c0.events.draw.connect
+            def draw00(event):
+                print('  {0:7}: {1}'.format('0', bgcolors[0]))
+                if bgcolors[0] is not None:
+                    gl.glViewport(0, 0, *list(_win_size))
+                    gl.glClearColor(*bgcolors[0])
+                    gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+                    gl.glFinish()
+
+            @c1.events.draw.connect
+            def draw11(event):
+                print('  {0:7}: {1}'.format('1', bgcolors[1]))
+                if bgcolors[1] is not None:
+                    gl.glViewport(0, 0, *list(_win_size))
+                    gl.glClearColor(*bgcolors[1])
+                    gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+                    gl.glFinish()
+
+            for ci, canvas in enumerate((c0, c1)):
+                print('draw %s' % canvas.title)
+                bgcolors[ci] = [0.5, 0.5, 0.5, 1.0]
+                _update_process_check(canvas, 127)
+
+            for ci, canvas in enumerate((c0, c1)):
+                print('test')
+                _update_process_check(canvas, 127, draw=False)
+                bgcolors[ci] = [1., 1., 1., 1.]
+                _update_process_check(canvas, 255)
+                bgcolors[ci] = [0.25, 0.25, 0.25, 0.25]
+                _update_process_check(canvas, 64)
diff --git a/vispy/app/timer.py b/vispy/app/timer.py
index 5d04540..144abf0 100644
--- a/vispy/app/timer.py
+++ b/vispy/app/timer.py
@@ -1,34 +1,66 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
-from __future__ import print_function, division, absolute_import
+from __future__ import division
 
-import vispy
-from vispy.util.event import Event, EmitterGroup
-from vispy.util.ptime import time as precision_time
+from ..util.event import Event, EmitterGroup
+from ..util.ptime import time as precision_time
+from ..ext.six import string_types
+from .base import BaseTimerBackend as TimerBackend  # noqa
+from . import use_app, Application
 
 
 class Timer(object):
-    """Timer used to schedule events in the future or on a repeating schedule.
+
+    """Timer used to schedule events in the future or on a repeating schedule
+
+    Parameters
+    ----------
+    interval : float | 'auto'
+        Time between events in seconds. The default is 'auto', which
+        attempts to find the interval that matches the refresh rate of
+        the current monitor. Currently this is simply 1/60.
+    connect : function | None
+        The function to call.
+    iterations : int
+        Number of iterations. Can be -1 for infinite.
+    start : bool
+        Whether to start the timer.
+    app : instance of vispy.app.Application
+        The application to attach the timer to.
     """
-    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)
+
+    def __init__(self, interval='auto', 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
+        if app is None:
+            self._app = use_app()
+        elif isinstance(app, Application):
+            self._app = app
+        elif isinstance(app, string_types):
+            self._app = Application(app)
+        else:
+            raise ValueError('Invalid value for app %r' % app)
         
-        # 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()
+        # Ensure app has backend app object
+        self._app.native
         
         # Instantiate the backed with the right class
         self._backend = self._app.backend_module.TimerBackend(self)
         
-        self._interval = interval
+        if interval == 'auto':
+            interval = 1.0 / 60
+        self._interval = float(interval)
         self._running = False
+        self._first_emit_time = None
         self._last_emit_time = None
         self.iter_count = 0
         self.max_iterations = iterations
@@ -36,14 +68,12 @@ class Timer(object):
             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):
@@ -57,18 +87,22 @@ class Timer(object):
             self.start()
 
     @property
+    def elapsed(self):
+        return precision_time() - self._first_emit_time
+
+    @property
     def running(self):
         return self._running
 
     def start(self, interval=None, iterations=None):
-        """Start the timer. 
+        """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 
+
+        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
@@ -78,33 +112,33 @@ class Timer(object):
             self.max_iterations = iterations
         self._backend._vispy_start(self.interval)
         self._running = True
-        self._last_emit_time = None
+        self._first_emit_time = precision_time()
+        self._last_emit_time = precision_time()
         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):
+    # def run_event_loop(self):
         #"""Execute the event loop for this Timer's backend.
         #"""
-        #return self._backend._vispy_run()
-        
-    #def quit_event_loop(self):
+        # return self._backend._vispy_run()
+
+    # def quit_event_loop(self):
         #"""Exit the event loop for this Timer's backend.
         #"""
-        #return self._backend._vispy_quit()
-    
+        # 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:
@@ -112,18 +146,20 @@ class Timer(object):
         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
+        dt = now - self._last_emit_time
+        elapsed = now - self._first_emit_time
         self._last_emit_time = now
-        
-        self.events.timeout(type='timer_timeout', iteration=self.iter_count, dt=dt)
+
+        self.events.timeout(
+            type='timer_timeout',
+            iteration=self.iter_count,
+            elapsed=elapsed,
+            dt=dt)
         self.iter_count += 1
-    
+
     def connect(self, callback):
         """ Alias for self.events.timeout.connect() """
         return self.events.timeout.connect(callback)
@@ -131,25 +167,3 @@ class Timer(object):
     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/color/__init__.py b/vispy/color/__init__.py
new file mode 100644
index 0000000..ae53d5d
--- /dev/null
+++ b/vispy/color/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Convience interfaces to manipulate colors.
+
+This module provides support for manipulating colors.
+"""
+
+__all__ = ['Color', 'ColorArray', 'LinearGradient', 'get_color_names']
+
+from ._color_dict import get_color_names  # noqa
+from ._color import Color, ColorArray, LinearGradient  # noqa
diff --git a/vispy/color/_color.py b/vispy/color/_color.py
new file mode 100644
index 0000000..3c2e48b
--- /dev/null
+++ b/vispy/color/_color.py
@@ -0,0 +1,606 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division  # just to be safe...
+
+import numpy as np
+from copy import deepcopy
+
+from ..ext.six import string_types
+from ..util import logger
+from ._color_dict import _color_dict
+
+
+###############################################################################
+# User-friendliness helpers
+
+def _string_to_rgb(color):
+    """Convert user string or hex color to color array (length 3 or 4)"""
+    if not color.startswith('#'):
+        if color.lower() not in _color_dict:
+            raise ValueError('Color "%s" unknown' % color)
+        color = _color_dict[color]
+        assert color[0] == '#'
+    # hex color
+    color = color[1:]
+    lc = len(color)
+    if lc in (3, 4):
+        color = ''.join(c + c for c in color)
+        lc = len(color)
+    if lc not in (6, 8):
+        raise ValueError('Hex color must have exactly six or eight '
+                         'elements following the # sign')
+    color = np.array([int(color[i:i+2], 16) / 255. for i in range(0, lc, 2)])
+    return color
+
+
+def _user_to_rgba(color, expand=True):
+    """Convert color(s) from any set of fmts (str/hex/arr) to RGB(A) array"""
+    if color is None:
+        color = np.zeros(4, np.float32)
+    if isinstance(color, string_types):
+        color = _string_to_rgb(color)
+    elif isinstance(color, ColorArray):
+        color = color.rgba
+    # We have to treat this specially
+    elif isinstance(color, (list, tuple)):
+        if any(isinstance(c, string_types) for c in color):
+            color = [_user_to_rgba(c) for c in color]
+            if any(len(c) > 1 for c in color):
+                raise RuntimeError('could not parse colors, are they nested?')
+            color = [c[0] for c in color]
+    color = np.atleast_2d(color).astype(np.float32)
+    if color.shape[1] not in (3, 4):
+        raise ValueError('color must have three or four elements')
+    if expand and color.shape[1] == 3:  # only expand if requested
+        color = np.concatenate((color, np.ones((color.shape[0], 1))),
+                               axis=1)
+    if color.min() < 0 or color.max() > 1:
+        logger.warning('Color will be clipped between 0 and 1: %s' % color)
+        color = np.clip(color, 0, 1)
+    return color
+
+
+def _check_color_dim(val):
+    """Ensure val is Nx(n_col), usually Nx3"""
+    val = np.atleast_2d(val)
+    if val.shape[1] not in (3, 4):
+        raise RuntimeError('Value must have second dimension of size 3 or 4')
+    return val, val.shape[1]
+
+
+def _array_clip_val(val):
+    """Helper to turn val into array and clip between 0 and 1"""
+    val = np.array(val)
+    if val.max() > 1 or val.min() < 0:
+        logger.warning('value will be clipped between 0 and 1')
+    val[...] = np.clip(val, 0, 1)
+    return val
+
+
+###############################################################################
+# RGB<->HEX conversion
+
+def _hex_to_rgba(hexs):
+    """Convert hex to rgba, permitting alpha values in hex"""
+    hexs = np.atleast_1d(np.array(hexs, '|U9'))
+    out = np.ones((len(hexs), 4), np.float32)
+    for hi, h in enumerate(hexs):
+        assert isinstance(h, string_types)
+        off = 1 if h[0] == '#' else 0
+        assert len(h) in (6+off, 8+off)
+        e = (len(h)-off) // 2
+        out[hi, :e] = [int(h[i:i+2], 16) / 255.
+                       for i in range(off, len(h), 2)]
+    return out
+
+
+def _rgb_to_hex(rgbs):
+    """Convert rgb to hex triplet"""
+    rgbs, n_dim = _check_color_dim(rgbs)
+    return np.array(['#%02x%02x%02x' % tuple((255*rgb[:3]).astype(np.uint8))
+                     for rgb in rgbs], '|U7')
+
+
+###############################################################################
+# RGB<->HSV conversion
+
+def _rgb_to_hsv(rgbs):
+    """Convert Nx3 or Nx4 rgb to hsv"""
+    rgbs, n_dim = _check_color_dim(rgbs)
+    hsvs = list()
+    for rgb in rgbs:
+        rgb = rgb[:3]  # don't use alpha here
+        idx = np.argmax(rgb)
+        val = rgb[idx]
+        c = val - np.min(rgb)
+        if c == 0:
+            hue = 0
+            sat = 0
+        else:
+            if idx == 0:  # R == max
+                hue = ((rgb[1] - rgb[2]) / c) % 6
+            elif idx == 1:  # G == max
+                hue = (rgb[2] - rgb[0]) / c + 2
+            else:  # B == max
+                hue = (rgb[0] - rgb[1]) / c + 4
+            hue *= 60
+            sat = c / val
+        hsv = [hue, sat, val]
+        hsvs.append(hsv)
+    hsvs = np.array(hsvs, dtype=np.float32)
+    if n_dim == 4:
+        hsvs = np.concatenate((hsvs, rgbs[:, 3]), axis=1)
+    return hsvs
+
+
+def _hsv_to_rgb(hsvs):
+    """Convert Nx3 or Nx4 hsv to rgb"""
+    hsvs, n_dim = _check_color_dim(hsvs)
+    # In principle, we *might* be able to vectorize this, but might as well
+    # wait until a compelling use case appears
+    rgbs = list()
+    for hsv in hsvs:
+        c = hsv[1] * hsv[2]
+        m = hsv[2] - c
+        hp = hsv[0] / 60
+        x = c * (1 - abs(hp % 2 - 1))
+        if 0 <= hp < 1:
+            r, g, b = c, x, 0
+        elif hp < 2:
+            r, g, b = x, c, 0
+        elif hp < 3:
+            r, g, b = 0, c, x
+        elif hp < 4:
+            r, g, b = 0, x, c
+        elif hp < 5:
+            r, g, b = x, 0, c
+        else:
+            r, g, b = c, 0, x
+        rgb = [r + m, g + m, b + m]
+        rgbs.append(rgb)
+    rgbs = np.array(rgbs, dtype=np.float32)
+    if n_dim == 4:
+        rgbs = np.concatenate((rgbs, hsvs[:, 3]), axis=1)
+    return rgbs
+
+
+###############################################################################
+# RGB<->CIELab conversion
+
+# These numbers are adapted from MIT-licensed MATLAB code for
+# Lab<->RGB conversion. They provide an XYZ<->RGB conversion matrices,
+# w/D65 white point normalization built in.
+
+#_rgb2xyz = np.array([[0.412453, 0.357580, 0.180423],
+#                     [0.212671, 0.715160, 0.072169],
+#                     [0.019334, 0.119193, 0.950227]])
+#_white_norm = np.array([0.950456, 1.0, 1.088754])
+#_rgb2xyz /= _white_norm[:, np.newaxis]
+#_rgb2xyz_norm = _rgb2xyz.T
+_rgb2xyz_norm = np.array([[0.43395276, 0.212671, 0.01775791],
+                         [0.37621941, 0.71516, 0.10947652],
+                         [0.18982783, 0.072169, 0.87276557]])
+
+#_xyz2rgb = np.array([[3.240479, -1.537150, -0.498535],
+#                     [-0.969256, 1.875992, 0.041556],
+#                     [0.055648, -0.204043, 1.057311]])
+#_white_norm = np.array([0.950456, 1., 1.088754])
+#_xyz2rgb *= _white_norm[np.newaxis, :]
+_xyz2rgb_norm = np.array([[3.07993271, -1.53715, -0.54278198],
+                          [-0.92123518, 1.875992, 0.04524426],
+                          [0.05289098, -0.204043, 1.15115158]])
+
+
+def _rgb_to_lab(rgbs):
+    rgbs, n_dim = _check_color_dim(rgbs)
+    # convert RGB->XYZ
+    xyz = rgbs[:, :3].copy()  # a misnomer for now but will end up being XYZ
+    over = xyz > 0.04045
+    xyz[over] = ((xyz[over] + 0.055) / 1.055) ** 2.4
+    xyz[~over] /= 12.92
+    xyz = np.dot(xyz, _rgb2xyz_norm)
+    over = xyz > 0.008856
+    xyz[over] = xyz[over] ** (1. / 3.)
+    xyz[~over] = 7.787 * xyz[~over] + 0.13793103448275862
+
+    # Convert XYZ->LAB
+    L = (116. * xyz[:, 1]) - 16
+    a = 500 * (xyz[:, 0] - xyz[:, 1])
+    b = 200 * (xyz[:, 1] - xyz[:, 2])
+    labs = [L, a, b]
+    # Append alpha if necessary
+    if n_dim == 4:
+        labs.append(np.atleast1d(rgbs[:, 3]))
+    labs = np.array(labs, order='F').T  # Becomes 'C' order b/c of .T
+    return labs
+
+
+def _lab_to_rgb(labs):
+    """Convert Nx3 or Nx4 lab to rgb"""
+    # adapted from BSD-licensed work in MATLAB by Mark Ruzon
+    # Based on ITU-R Recommendation BT.709 using the D65
+    labs, n_dim = _check_color_dim(labs)
+
+    # Convert Lab->XYZ (silly indexing used to preserve dimensionality)
+    y = (labs[:, 0] + 16.) / 116.
+    x = (labs[:, 1] / 500.) + y
+    z = y - (labs[:, 2] / 200.)
+    xyz = np.concatenate(([x], [y], [z]))  # 3xN
+    over = xyz > 0.2068966
+    xyz[over] = xyz[over] ** 3.
+    xyz[~over] = (xyz[~over] - 0.13793103448275862) / 7.787
+
+    # Convert XYZ->LAB
+    rgbs = np.dot(_xyz2rgb_norm, xyz).T
+    over = rgbs > 0.0031308
+    rgbs[over] = 1.055 * (rgbs[over] ** (1. / 2.4)) - 0.055
+    rgbs[~over] *= 12.92
+    if n_dim == 4:
+        rgbs = np.concatenate((rgbs, labs[:, 3]), axis=1)
+    rgbs = np.clip(rgbs, 0., 1.)
+    return rgbs
+
+
+###############################################################################
+# Now for the user-level classes
+
+class ColorArray(object):
+    """An array of colors
+
+    Parameters
+    ----------
+    color : str | tuple | list of colors
+        If str, can be any of the names in ``vispy.color.get_color_names``.
+        Can also be a hex value if it starts with ``'#'`` as ``'#ff0000'``.
+        If array-like, it must be an Nx3 or Nx4 array-like object.
+        Can also be a list of colors, such as
+        ``['red', '#00ff00', ColorArray('blue')]``.
+    alpha : float | None
+        If no alpha is not supplied in ``color`` entry and ``alpha`` is None,
+        then this will default to 1.0 (opaque). If float, it will override
+        any alpha values in ``color``, if provided.
+
+    Examples
+    --------
+    There are many ways to define colors. Here are some basic cases:
+
+        >>> from vispy.color import ColorArray
+        >>> r = ColorArray('red')  # using string name
+        >>> r
+        <ColorArray: 1 color ((1.0, 0.0, 0.0, 1.0))>
+        >>> g = ColorArray((0, 1, 0, 1))  # RGBA tuple
+        >>> b = ColorArray('#0000ff')  # hex color
+        >>> w = ColorArray()  # defaults to black
+        >>> w.rgb = r.rgb + g.rgb + b.rgb
+        >>> w == ColorArray('white')
+        True
+        >>> w.alpha = 0
+        >>> w
+        <ColorArray: 1 color ((1.0, 1.0, 1.0, 0.0))>
+        >>> rgb = ColorArray(['r', (0, 1, 0), '#0000FFFF'])
+        >>> rgb
+        <ColorArray: 3 colors ((1.0, 0.0, 0.0, 1.0) ... (1.0, 0.0, 0.0, 1.0))>
+        >>> rgb == ColorArray(['red', '#00ff00', ColorArray('blue')])
+        True
+
+    Notes
+    -----
+    Under the hood, this class stores data in RGBA format suitable for use
+    on the GPU.
+    """
+    def __init__(self, color='black', alpha=None):
+        """Parse input type, and set attribute"""
+        rgba = _user_to_rgba(color)
+        if alpha is not None:
+            rgba[:, 3] = alpha
+        self._rgba = None
+        self.rgba = rgba
+
+    ###########################################################################
+    # Builtins and utilities
+    def copy(self):
+        """Return a copy"""
+        return deepcopy(self)
+
+    @classmethod
+    def _name(cls):
+        """Helper to get the class name once it's been created"""
+        return cls.__name__
+
+    def __len__(self):
+        return self._rgba.shape[0]
+
+    def __repr__(self):
+        nice_str = str(tuple(self._rgba[0]))
+        plural = ''
+        if len(self) > 1:
+            plural = 's'
+            nice_str += ' ... ' + str(tuple(self.rgba[-1]))
+        # use self._name() here instead of hard-coding name in case
+        # we eventually subclass this class
+        return ('<%s: %i color%s (%s)>' % (self._name(), len(self),
+                                           plural, nice_str))
+
+    def __eq__(self, other):
+        return np.array_equal(self._rgba, other._rgba)
+
+    ###########################################################################
+    def __getitem__(self, item):
+        if isinstance(item, tuple):
+            raise ValueError('ColorArray indexing is only allowed along '
+                             'the first dimension.')
+        subrgba = self._rgba[item]
+        if subrgba.ndim == 1:
+            assert len(subrgba) == 4
+        elif subrgba.ndim == 2:
+            assert subrgba.shape[1] in (3, 4)
+        return ColorArray(subrgba)
+
+    def __setitem__(self, item, value):
+        if isinstance(item, tuple):
+            raise ValueError('ColorArray indexing is only allowed along '
+                             'the first dimension.')
+        # value should be a RGBA array, or a ColorArray instance
+        if isinstance(value, ColorArray):
+            value = value.rgba
+        self._rgba[item] = value
+
+    # RGB(A)
+    @property
+    def rgba(self):
+        """Nx4 array of RGBA floats"""
+        return self._rgba.copy()
+
+    @rgba.setter
+    def rgba(self, val):
+        """Set the color using an Nx4 array of RGBA floats"""
+        # Note: all other attribute sets get routed here!
+        # This method is meant to do the heavy lifting of setting data
+        rgba = _user_to_rgba(val, expand=False)
+        if self._rgba is None:
+            self._rgba = rgba  # only on init
+        else:
+            self._rgba[:, :rgba.shape[1]] = rgba
+
+    @property
+    def rgb(self):
+        """Nx3 array of RGB floats"""
+        return self._rgba[:, :3].copy()
+
+    @rgb.setter
+    def rgb(self, val):
+        """Set the color using an Nx3 array of RGB floats"""
+        self.rgba = val
+
+    @property
+    def RGBA(self):
+        """Nx4 array of RGBA uint8s"""
+        return (self._rgba * 255).astype(np.uint8)
+
+    @RGBA.setter
+    def RGBA(self, val):
+        """Set the color using an Nx4 array of RGBA uint8 values"""
+        # need to convert to normalized float
+        val = np.atleast_1d(val).astype(np.float32) / 255
+        self.rgba = val
+
+    @property
+    def RGB(self):
+        """Nx3 array of RGBA uint8s"""
+        return np.round(self._rgba[:, :3] * 255).astype(int)
+
+    @RGB.setter
+    def RGB(self, val):
+        """Set the color using an Nx3 array of RGB uint8 values"""
+        # need to convert to normalized float
+        val = np.atleast_1d(val).astype(np.float32) / 255.
+        self.rgba = val
+
+    @property
+    def alpha(self):
+        """Length-N array of alpha floats"""
+        return self._rgba[:, 3]
+
+    @alpha.setter
+    def alpha(self, val):
+        """Set the color using alpha"""
+        self._rgba[:, 3] = _array_clip_val(val)
+
+    ###########################################################################
+    # HEX
+    @property
+    def hex(self):
+        """Numpy array with N elements, each one a hex triplet string"""
+        return _rgb_to_hex(self._rgba)
+
+    @hex.setter
+    def hex(self, val):
+        """Set the color values using a list of hex strings"""
+        self.rgba = _hex_to_rgba(val)
+
+    ###########################################################################
+    # HSV
+    @property
+    def hsv(self):
+        """Nx3 array of HSV floats"""
+        return self._hsv
+
+    @hsv.setter
+    def hsv(self, val):
+        """Set the color values using an Nx3 array of HSV floats"""
+        self.rgba = _hsv_to_rgb(val)
+
+    @property
+    def _hsv(self):
+        """Nx3 array of HSV floats"""
+        # this is done privately so that overriding functions work
+        return _rgb_to_hsv(self._rgba[:, :3])
+
+    @property
+    def value(self):
+        """Length-N array of color HSV values"""
+        return self._hsv[:, 2]
+
+    @value.setter
+    def value(self, val):
+        """Set the color using length-N array of (from HSV)"""
+        hsv = self._hsv
+        hsv[:, 2] = _array_clip_val(val)
+        self.rgba = _hsv_to_rgb(hsv)
+
+    def lighter(self, dv=0.1, copy=True):
+        """Produce a lighter color (if possible)
+
+        Parameters
+        ----------
+        dv : float
+            Amount to increase the color value by.
+        copy : bool
+            If False, operation will be carried out in-place.
+
+        Returns
+        -------
+        color : instance of ColorArray
+            The lightened Color.
+        """
+        color = self.copy() if copy else self
+        color.value += dv
+        return color
+
+    def darker(self, dv=0.1, copy=True):
+        """Produce a darker color (if possible)
+
+        Parameters
+        ----------
+        dv : float
+            Amount to decrease the color value by.
+        copy : bool
+            If False, operation will be carried out in-place.
+
+        Returns
+        -------
+        color : instance of ColorArray
+            The darkened Color.
+        """
+        color = self.copy() if copy else self
+        color.value -= dv
+        return color
+
+    ###########################################################################
+    # Lab
+    @property
+    def lab(self):
+        return _rgb_to_lab(self._rgba[:, :3])
+
+    @lab.setter
+    def lab(self, val):
+        self.rgba = _lab_to_rgb(val)
+
+
+class LinearGradient(ColorArray):
+    """Class to represent linear gradients
+
+    Parameters
+    ----------
+    colors : ColorArray
+        The control points to use as colors.
+    x : array
+        Array of the same length as ``colors`` that give the x-values
+        to use along the axis of the array.
+    """
+    def __init__(self, colors, x):
+        ColorArray.__init__(self, colors)
+        self.gradient_x = x
+
+    @property
+    def gradient_x(self):
+        return self._grad_x.copy()
+
+    @gradient_x.setter
+    def gradient_x(self, val):
+        x = np.array(val, dtype=np.float32)
+        if x.ndim != 1 or x.size != len(self):
+            raise ValueError('x must 1D with the same size as colors (%s), '
+                             'not %s' % (len(self), x.shape))
+        self._grad_x = x
+
+    def __getitem__(self, loc):
+        try:
+            loc = float(loc)
+        except:
+            raise RuntimeError('location could not be converted to float: %s'
+                               % str(loc))
+        rgba = [np.interp(loc, self._grad_x, rr) for rr in self._rgba.T]
+        return np.array(rgba)
+
+
+class Color(ColorArray):
+    """A single color
+
+    Parameters
+    ----------
+    color : str | tuple
+        If str, can be any of the names in ``vispy.color.get_color_names``.
+        Can also be a hex value if it starts with ``'#'`` as ``'#ff0000'``.
+        If array-like, it must be an 1-dimensional array with 3 or 4 elements.
+    alpha : float | None
+        If no alpha is not supplied in ``color`` entry and ``alpha`` is None,
+        then this will default to 1.0 (opaque). If float, it will override
+        the alpha value in ``color``, if provided.
+    """
+    def __init__(self, color='black', alpha=None):
+        """Parse input type, and set attribute"""
+        if isinstance(color, (list, tuple)):
+            color = np.array(color, np.float32)
+        rgba = _user_to_rgba(color)
+        if rgba.shape[0] != 1:
+            raise ValueError('color must be of correct shape')
+        if alpha is not None:
+            rgba[:, 3] = alpha
+        self._rgba = None
+        self.rgba = rgba.ravel()
+
+    @ColorArray.rgba.getter
+    def rgba(self):
+        return super(Color, self).rgba[0]
+
+    @ColorArray.rgb.getter
+    def rgb(self):
+        return super(Color, self).rgb[0]
+
+    @ColorArray.RGBA.getter
+    def RGBA(self):
+        return super(Color, self).RGBA[0]
+
+    @ColorArray.RGB.getter
+    def RGB(self):
+        return super(Color, self).RGB[0]
+
+    @ColorArray.alpha.getter
+    def alpha(self):
+        return super(Color, self).alpha[0]
+
+    @ColorArray.hex.getter
+    def hex(self):
+        return super(Color, self).hex[0]
+
+    @ColorArray.hsv.getter
+    def hsv(self):
+        return super(Color, self).hsv[0]
+
+    @ColorArray.value.getter
+    def value(self):
+        return super(Color, self).value[0]
+
+    @ColorArray.lab.getter
+    def lab(self):
+        return super(Color, self).lab[0]
+
+    def is_blank(self):
+        return self.rgba[3] == 0
+
+    def __repr__(self):
+        nice_str = str(tuple(self._rgba[0]))
+        return ('<%s: %s>' % (self._name(), nice_str))
diff --git a/vispy/color/_color_dict.py b/vispy/color/_color_dict.py
new file mode 100644
index 0000000..6b77a8e
--- /dev/null
+++ b/vispy/color/_color_dict.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+
+def get_color_names():
+    """Get the known color names
+
+    Returns
+    -------
+    names : list
+        List of color names known by vispy.
+    """
+    names = list(_color_dict.keys())
+    names.sort()
+    return names
+
+
+# This is used by color functions to translate user strings to colors
+# For now, this is web colors, and all in hex. It will take some simple
+# but annoying refactoring to deal with non-hex entries if we want them.
+
+# Add the CSS colors, courtesy MIT-licensed code from Dave Eddy:
+# github.com/bahamas10/css-color-names/blob/master/css-color-names.json
+
+_color_dict = {
+    "w": '#FFFFFF',
+    "k": '#000000',
+    "r": '#FF0000',
+    "g": '#00FF00',
+    "b": '#0000FF',
+    "y": '#FFFF00',
+    "m": '#FF00FF',
+    "c": '#00FFFF',
+    "aqua": "#00ffff",
+    "aliceblue": "#f0f8ff",
+    "antiquewhite": "#faebd7",
+    "black": "#000000",
+    "blue": "#0000ff",
+    "cyan": "#00ffff",
+    "darkblue": "#00008b",
+    "darkcyan": "#008b8b",
+    "darkgreen": "#006400",
+    "darkturquoise": "#00ced1",
+    "deepskyblue": "#00bfff",
+    "green": "#008000",
+    "lime": "#00ff00",
+    "mediumblue": "#0000cd",
+    "mediumspringgreen": "#00fa9a",
+    "navy": "#000080",
+    "springgreen": "#00ff7f",
+    "teal": "#008080",
+    "midnightblue": "#191970",
+    "dodgerblue": "#1e90ff",
+    "lightseagreen": "#20b2aa",
+    "forestgreen": "#228b22",
+    "seagreen": "#2e8b57",
+    "darkslategray": "#2f4f4f",
+    "darkslategrey": "#2f4f4f",
+    "limegreen": "#32cd32",
+    "mediumseagreen": "#3cb371",
+    "turquoise": "#40e0d0",
+    "royalblue": "#4169e1",
+    "steelblue": "#4682b4",
+    "darkslateblue": "#483d8b",
+    "mediumturquoise": "#48d1cc",
+    "indigo": "#4b0082",
+    "darkolivegreen": "#556b2f",
+    "cadetblue": "#5f9ea0",
+    "cornflowerblue": "#6495ed",
+    "mediumaquamarine": "#66cdaa",
+    "dimgray": "#696969",
+    "dimgrey": "#696969",
+    "slateblue": "#6a5acd",
+    "olivedrab": "#6b8e23",
+    "slategray": "#708090",
+    "slategrey": "#708090",
+    "lightslategray": "#778899",
+    "lightslategrey": "#778899",
+    "mediumslateblue": "#7b68ee",
+    "lawngreen": "#7cfc00",
+    "aquamarine": "#7fffd4",
+    "chartreuse": "#7fff00",
+    "gray": "#808080",
+    "grey": "#808080",
+    "maroon": "#800000",
+    "olive": "#808000",
+    "purple": "#800080",
+    "lightskyblue": "#87cefa",
+    "skyblue": "#87ceeb",
+    "blueviolet": "#8a2be2",
+    "darkmagenta": "#8b008b",
+    "darkred": "#8b0000",
+    "saddlebrown": "#8b4513",
+    "darkseagreen": "#8fbc8f",
+    "lightgreen": "#90ee90",
+    "mediumpurple": "#9370db",
+    "darkviolet": "#9400d3",
+    "palegreen": "#98fb98",
+    "darkorchid": "#9932cc",
+    "yellowgreen": "#9acd32",
+    "sienna": "#a0522d",
+    "brown": "#a52a2a",
+    "darkgray": "#a9a9a9",
+    "darkgrey": "#a9a9a9",
+    "greenyellow": "#adff2f",
+    "lightblue": "#add8e6",
+    "paleturquoise": "#afeeee",
+    "lightsteelblue": "#b0c4de",
+    "powderblue": "#b0e0e6",
+    "firebrick": "#b22222",
+    "darkgoldenrod": "#b8860b",
+    "mediumorchid": "#ba55d3",
+    "rosybrown": "#bc8f8f",
+    "darkkhaki": "#bdb76b",
+    "silver": "#c0c0c0",
+    "mediumvioletred": "#c71585",
+    "indianred": "#cd5c5c",
+    "peru": "#cd853f",
+    "chocolate": "#d2691e",
+    "tan": "#d2b48c",
+    "lightgray": "#d3d3d3",
+    "lightgrey": "#d3d3d3",
+    "thistle": "#d8bfd8",
+    "goldenrod": "#daa520",
+    "orchid": "#da70d6",
+    "palevioletred": "#db7093",
+    "crimson": "#dc143c",
+    "gainsboro": "#dcdcdc",
+    "plum": "#dda0dd",
+    "burlywood": "#deb887",
+    "lightcyan": "#e0ffff",
+    "lavender": "#e6e6fa",
+    "darksalmon": "#e9967a",
+    "palegoldenrod": "#eee8aa",
+    "violet": "#ee82ee",
+    "azure": "#f0ffff",
+    "honeydew": "#f0fff0",
+    "khaki": "#f0e68c",
+    "lightcoral": "#f08080",
+    "sandybrown": "#f4a460",
+    "beige": "#f5f5dc",
+    "mintcream": "#f5fffa",
+    "wheat": "#f5deb3",
+    "whitesmoke": "#f5f5f5",
+    "ghostwhite": "#f8f8ff",
+    "lightgoldenrodyellow": "#fafad2",
+    "linen": "#faf0e6",
+    "salmon": "#fa8072",
+    "oldlace": "#fdf5e6",
+    "bisque": "#ffe4c4",
+    "blanchedalmond": "#ffebcd",
+    "coral": "#ff7f50",
+    "cornsilk": "#fff8dc",
+    "darkorange": "#ff8c00",
+    "deeppink": "#ff1493",
+    "floralwhite": "#fffaf0",
+    "fuchsia": "#ff00ff",
+    "gold": "#ffd700",
+    "hotpink": "#ff69b4",
+    "ivory": "#fffff0",
+    "lavenderblush": "#fff0f5",
+    "lemonchiffon": "#fffacd",
+    "lightpink": "#ffb6c1",
+    "lightsalmon": "#ffa07a",
+    "lightyellow": "#ffffe0",
+    "magenta": "#ff00ff",
+    "mistyrose": "#ffe4e1",
+    "moccasin": "#ffe4b5",
+    "navajowhite": "#ffdead",
+    "orange": "#ffa500",
+    "orangered": "#ff4500",
+    "papayawhip": "#ffefd5",
+    "peachpuff": "#ffdab9",
+    "pink": "#ffc0cb",
+    "red": "#ff0000",
+    "seashell": "#fff5ee",
+    "snow": "#fffafa",
+    "tomato": "#ff6347",
+    "white": "#ffffff",
+    "yellow": "#ffff00",
+}
diff --git a/vispy/shaders/__init__.py b/vispy/color/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/color/tests/__init__.py
diff --git a/vispy/color/tests/test_color.py b/vispy/color/tests/test_color.py
new file mode 100644
index 0000000..1297a6b
--- /dev/null
+++ b/vispy/color/tests/test_color.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+from nose.tools import assert_equal, assert_raises, assert_true
+from numpy.testing import assert_array_equal, assert_allclose
+
+from vispy.color import Color, ColorArray, LinearGradient, get_color_names
+from vispy.util import use_log_level
+
+
+def test_color():
+    """Basic tests for Color class"""
+    x = Color('white')
+    assert_array_equal(x.rgba, [1.] * 4)
+    assert_array_equal(x.rgb, [1.] * 3)
+    assert_array_equal(x.RGBA, [255] * 4)
+    assert_array_equal(x.RGB, [255] * 3)
+    assert_equal(x.value, 1.)
+    assert_equal(x.alpha, 1.)
+    x.rgb = [0, 0, 1]
+    assert_array_equal(x.hsv, [240, 1, 1])
+    assert_equal(x.hex, '#0000ff')
+    x.hex = '#00000000'
+    assert_array_equal(x.rgba, [0.]*4)
+
+
+def test_color_array():
+    """Basic tests for ColorArray class"""
+    x = ColorArray(['r', 'g', 'b'])
+    assert_array_equal(x.rgb, np.eye(3))
+    # Test ColorArray.__getitem__.
+    assert isinstance(x[0], ColorArray)
+    assert isinstance(x[:], ColorArray)
+    assert_array_equal(x.rgba[:], x[:].rgba)
+    assert_array_equal(x.rgba[0], x[0].rgba.squeeze())
+    assert_array_equal(x.rgba[1:3], x[1:3].rgba)
+    assert_raises(ValueError, x.__getitem__, (0, 1))
+    # Test ColorArray.__setitem__.
+    x[0] = 0
+    assert_array_equal(x.rgba[0, :], np.zeros(4))
+    assert_array_equal(x.rgba, x[:].rgba)
+    x[1] = 1
+    assert_array_equal(x[1].rgba, np.ones((1, 4)))
+    x[:] = .5
+    assert_array_equal(x.rgba, .5 * np.ones((3, 4)))
+    assert_raises(ValueError, x.__setitem__, (0, 1), 0)
+
+
+def test_color_interpretation():
+    """Test basic color interpretation API"""
+    # test useful ways of single color init
+    r = ColorArray('r')
+    print(r)  # test repr
+    r2 = ColorArray(r)
+    assert_equal(r, r2)
+    r2.rgb = 0, 0, 0
+    assert_equal(r2, ColorArray('black'))
+    assert_equal(r, ColorArray('r'))  # modifying new one preserves old
+    assert_equal(r, r.copy())
+    assert_equal(r, ColorArray('#ff0000'))
+    assert_equal(r, ColorArray('#FF0000FF'))
+    assert_equal(r, ColorArray('red'))
+    assert_equal(r, ColorArray('red', alpha=1.0))
+    assert_equal(ColorArray((1, 0, 0, 0.1)), ColorArray('red', alpha=0.1))
+    assert_array_equal(r.rgb.ravel(), (1., 0., 0.))
+    assert_array_equal(r.rgba.ravel(), (1., 0., 0., 1.))
+    assert_array_equal(r.RGBA.ravel(), (255, 0, 0, 255))
+
+    # handling multiple colors
+    rgb = ColorArray(list('rgb'))
+    print(rgb)  # multi repr
+    assert_array_equal(rgb, ColorArray(np.eye(3)))
+    # complex/annoying case
+    rgb = ColorArray(['r', (0, 1, 0), '#0000ffff'])
+    assert_array_equal(rgb, ColorArray(np.eye(3)))
+    assert_raises(RuntimeError, ColorArray, ['r', np.eye(3)])  # can't nest
+
+    # getting/setting properties
+    r = ColorArray('#ffff')
+    assert_equal(r, ColorArray('white'))
+    r = ColorArray('#ff000000')
+    assert_true('turquoise' in get_color_names())  # make sure our JSON loaded
+    assert_equal(r.alpha, 0)
+    r.alpha = 1.0
+    assert_equal(r, ColorArray('r'))
+    r.alpha = 0
+    r.rgb = (1, 0, 0)
+    assert_equal(r.alpha, 0)
+    assert_equal(r.hex, ['#ff0000'])
+    r.alpha = 1
+    r.hex = '00ff00'
+    assert_equal(r, ColorArray('g'))
+    assert_array_equal(r.rgb.ravel(), (0., 1., 0.))
+    r.RGB = 255, 0, 0
+    assert_equal(r, ColorArray('r'))
+    assert_array_equal(r.RGB.ravel(), (255, 0, 0))
+    r.RGBA = 255, 0, 0, 0
+    assert_equal(r, ColorArray('r', alpha=0))
+    w = ColorArray()
+    w.rgb = ColorArray('r').rgb + ColorArray('g').rgb + ColorArray('b').rgb
+    assert_equal(w, ColorArray('white'))
+    w = ColorArray('white')
+    assert_equal(w, w.darker().lighter())
+    assert_equal(w, w.darker(0.1).darker(-0.1))
+    w2 = w.darker()
+    assert_true(w != w2)
+    w.darker(copy=False)
+    assert_equal(w, w2)
+    with use_log_level('warning', record=True, print_msg=False) as w:
+        w = ColorArray('white')
+        w.value = 2
+        assert_equal(len(w), 1)
+    assert_equal(w, ColorArray('white'))
+
+    # warnings and errors
+    assert_raises(ValueError, ColorArray, '#ffii00')  # non-hex
+    assert_raises(ValueError, ColorArray, '#ff000')  # too short
+    assert_raises(ValueError, ColorArray, [0, 0])  # not enough vals
+    with use_log_level('warning', record=True, print_msg=False) as w:
+        c = ColorArray([2., 0., 0.])  # val > 1
+        assert_true(np.all(c.rgb <= 1))
+        c = ColorArray([-1., 0., 0.])  # val < 0
+        assert_true(np.all(c.rgb >= 0))
+        assert_equal(len(w), 2)  # caught warnings
+    # make sure our color dict works
+    for key in get_color_names():
+        assert_true(ColorArray(key))
+    assert_raises(ValueError, ColorArray, 'foo')  # unknown color error
+
+
+# Taken from known values
+hsv_dict = dict(red=(0, 1, 1),
+                lime=(120, 1, 1),
+                yellow=(60, 1, 1),
+                silver=(0, 0, 0.75),
+                olive=(60, 1, 0.5),
+                purple=(300, 1, 0.5),
+                navy=(240, 1, 0.5))
+
+# Taken from skimage conversions
+lab_dict = dict(red=(53.2405879437448, 80.0941668344849, 67.2015369950715),
+                lime=(87.7350994883189, -86.1812575110439, 83.1774770684517),
+                yellow=(97.1395070397132, -21.5523924360088, 94.4757817840079),
+                black=(0., 0., 0.),
+                white=(100., 0., 0.),
+                gray=(53.5850240, 0., 0.),
+                olive=(51.86909754, -12.93002583, 56.67467593))
+
+
+def test_color_conversion():
+    """Test color conversions"""
+    # HSV
+    # test known values
+    test = ColorArray()
+    for key in hsv_dict:
+        c = ColorArray(key)
+        test.hsv = hsv_dict[key]
+        assert_allclose(c.RGB, test.RGB, atol=1)
+    test.value = 0
+    assert_equal(test.value, 0)
+    assert_equal(test, ColorArray('black'))
+    c = ColorArray('black')
+    assert_array_equal(c.hsv.ravel(), (0, 0, 0))
+    rng = np.random.RandomState(0)
+    for _ in range(50):
+        hsv = rng.rand(3)
+        hsv[0] *= 360
+        hsv[1] = hsv[1] * 0.99 + 0.01  # avoid ugly boundary effects
+        hsv[2] = hsv[2] * 0.99 + 0.01
+        c.hsv = hsv
+        assert_allclose(c.hsv.ravel(), hsv, rtol=1e-4, atol=1e-4)
+
+    # Lab
+    test = ColorArray()
+    for key in lab_dict:
+        c = ColorArray(key)
+        test.lab = lab_dict[key]
+        assert_allclose(c.rgba, test.rgba, atol=1e-4, rtol=1e-4)
+        assert_allclose(test.lab.ravel(), lab_dict[key], atol=1e-4, rtol=1e-4)
+    for _ in range(50):
+        # boundaries can have ugly rounding errors in some parameters
+        rgb = (rng.rand(3)[np.newaxis, :] * 0.9 + 0.05)
+        c.rgb = rgb
+        lab = c.lab
+        c.lab = lab
+        assert_allclose(c.lab, lab, atol=1e-4, rtol=1e-4)
+        assert_allclose(c.rgb, rgb, atol=1e-4, rtol=1e-4)
+
+
+def test_linear_gradient():
+    """Test basic support for linear gradients"""
+    colors = ['r', 'g', 'b']
+    xs = [0, 1, 2]
+    grad = LinearGradient(ColorArray(colors), xs)
+    colors.extend([[0.5, 0.5, 0], [0, 0, 1], [1, 0, 0]])
+    xs.extend([0.5, 10, -10])
+    for x, c in zip(xs, colors):
+        assert_array_equal(grad[x], ColorArray(c).rgba[0])
diff --git a/vispy/data/crate.bz2 b/vispy/data/crate.bz2
deleted file mode 100644
index e3fe0ec..0000000
Binary files a/vispy/data/crate.bz2 and /dev/null differ
diff --git a/vispy/data/cube.obj b/vispy/data/cube.obj
deleted file mode 100644
index 1496c12..0000000
--- a/vispy/data/cube.obj
+++ /dev/null
@@ -1,36 +0,0 @@
-# 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
deleted file mode 100644
index 51f93db..0000000
--- a/vispy/data/triceratops.obj
+++ /dev/null
@@ -1,8492 +0,0 @@
-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/shaders/__init__.py b/vispy/ext/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/ext/__init__.py
diff --git a/vispy/ext/_mpl_py3k_compat.py b/vispy/ext/_mpl_py3k_compat.py
new file mode 100644
index 0000000..9ca8455
--- /dev/null
+++ b/vispy/ext/_mpl_py3k_compat.py
@@ -0,0 +1,22 @@
+"""
+Simple fixes for Python 2/3 compatibility
+"""
+import sys
+PY3K = sys.version_info[0] >= 3
+
+
+if PY3K:
+    import builtins
+    import functools
+    reduce = functools.reduce
+    zip = builtins.zip
+    xrange = builtins.range
+    map = builtins.map
+else:
+    import __builtin__
+    import itertools
+    builtins = __builtin__
+    reduce = __builtin__.reduce
+    zip = itertools.izip
+    xrange = __builtin__.xrange
+    map = itertools.imap
diff --git a/vispy/ext/cocoapy.py b/vispy/ext/cocoapy.py
new file mode 100644
index 0000000..dbf662c
--- /dev/null
+++ b/vispy/ext/cocoapy.py
@@ -0,0 +1,1507 @@
+# -*- coding: utf-8 -*-
+
+from ctypes import (cdll, util, Structure, cast, byref, POINTER, CFUNCTYPE,
+                    c_int, c_long, c_ulong, c_ushort, c_wchar, c_uint32,
+                    c_double, c_uint, c_float, c_void_p, c_char_p, c_bool,
+                    c_buffer, c_ubyte, c_byte, c_int8, c_int16, c_int32,
+                    c_int64, c_short, c_longlong, c_size_t, sizeof,
+                    c_uint8, c_longdouble, c_char, c_ulonglong, py_object,
+                    alignment, ArgumentError)
+
+import platform
+import struct
+
+
+# Based on Pyglet code
+
+##############################################################################
+# cocoatypes.py
+
+__LP64__ = (8 * struct.calcsize("P") == 64)
+__i386__ = (platform.machine() == 'i386')
+
+PyObjectEncoding = b'{PyObject=@}'
+
+
+def encoding_for_ctype(vartype):
+    typecodes = {c_char: b'c', c_int: b'i', c_short: b's', c_long: b'l',
+                 c_longlong: b'q', c_ubyte: b'C', c_uint: b'I', c_ushort: b'S',
+                 c_ulong: b'L', c_ulonglong: b'Q', c_float: b'f',
+                 c_double: b'd', c_bool: b'B', c_char_p: b'*', c_void_p: b'@',
+                 py_object: PyObjectEncoding}
+    return typecodes.get(vartype, b'?')
+
+if __LP64__:
+    NSInteger = c_long
+    NSUInteger = c_ulong
+    CGFloat = c_double
+    NSPointEncoding = b'{CGPoint=dd}'
+    NSSizeEncoding = b'{CGSize=dd}'
+    NSRectEncoding = b'{CGRect={CGPoint=dd}{CGSize=dd}}'
+    NSRangeEncoding = b'{_NSRange=QQ}'
+else:
+    NSInteger = c_int
+    NSUInteger = c_uint
+    CGFloat = c_float
+    NSPointEncoding = b'{_NSPoint=ff}'
+    NSSizeEncoding = b'{_NSSize=ff}'
+    NSRectEncoding = b'{_NSRect={_NSPoint=ff}{_NSSize=ff}}'
+    NSRangeEncoding = b'{_NSRange=II}'
+
+NSIntegerEncoding = encoding_for_ctype(NSInteger)
+NSUIntegerEncoding = encoding_for_ctype(NSUInteger)
+CGFloatEncoding = encoding_for_ctype(CGFloat)
+
+CGImageEncoding = b'{CGImage=}'
+NSZoneEncoding = b'{_NSZone=}'
+
+
+class NSPoint(Structure):
+    _fields_ = [("x", CGFloat), ("y", CGFloat)]
+CGPoint = NSPoint
+
+
+class NSSize(Structure):
+    _fields_ = [("width", CGFloat), ("height", CGFloat)]
+CGSize = NSSize
+
+
+class NSRect(Structure):
+    _fields_ = [("origin", NSPoint), ("size", NSSize)]
+CGRect = NSRect
+
+
+NSTimeInterval = c_double
+CFIndex = c_long
+UniChar = c_ushort
+unichar = c_wchar
+CGGlyph = c_ushort
+
+
+class CFRange(Structure):
+    _fields_ = [("location", CFIndex), ("length", CFIndex)]
+
+
+class NSRange(Structure):
+    _fields_ = [("location", NSUInteger), ("length", NSUInteger)]
+
+
+CFTypeID = c_ulong
+CFNumberType = c_uint32
+
+
+##############################################################################
+# runtime.py
+
+__LP64__ = (8*struct.calcsize("P") == 64)
+__i386__ = (platform.machine() == 'i386')
+
+if sizeof(c_void_p) == 4:
+    c_ptrdiff_t = c_int32
+elif sizeof(c_void_p) == 8:
+    c_ptrdiff_t = c_int64
+
+objc = cdll.LoadLibrary(util.find_library('objc'))
+
+objc.class_addIvar.restype = c_bool
+objc.class_addIvar.argtypes = [c_void_p, c_char_p, c_size_t, c_uint8, c_char_p]
+
+objc.class_addMethod.restype = c_bool
+
+objc.class_addProtocol.restype = c_bool
+objc.class_addProtocol.argtypes = [c_void_p, c_void_p]
+
+objc.class_conformsToProtocol.restype = c_bool
+objc.class_conformsToProtocol.argtypes = [c_void_p, c_void_p]
+
+objc.class_copyIvarList.restype = POINTER(c_void_p)
+objc.class_copyIvarList.argtypes = [c_void_p, POINTER(c_uint)]
+
+objc.class_copyMethodList.restype = POINTER(c_void_p)
+objc.class_copyMethodList.argtypes = [c_void_p, POINTER(c_uint)]
+
+objc.class_copyPropertyList.restype = POINTER(c_void_p)
+objc.class_copyPropertyList.argtypes = [c_void_p, POINTER(c_uint)]
+
+objc.class_copyProtocolList.restype = POINTER(c_void_p)
+objc.class_copyProtocolList.argtypes = [c_void_p, POINTER(c_uint)]
+
+objc.class_createInstance.restype = c_void_p
+objc.class_createInstance.argtypes = [c_void_p, c_size_t]
+
+objc.class_getClassMethod.restype = c_void_p
+objc.class_getClassMethod.argtypes = [c_void_p, c_void_p]
+
+objc.class_getClassVariable.restype = c_void_p
+objc.class_getClassVariable.argtypes = [c_void_p, c_char_p]
+
+objc.class_getInstanceMethod.restype = c_void_p
+objc.class_getInstanceMethod.argtypes = [c_void_p, c_void_p]
+
+objc.class_getInstanceSize.restype = c_size_t
+objc.class_getInstanceSize.argtypes = [c_void_p]
+
+objc.class_getInstanceVariable.restype = c_void_p
+objc.class_getInstanceVariable.argtypes = [c_void_p, c_char_p]
+
+objc.class_getIvarLayout.restype = c_char_p
+objc.class_getIvarLayout.argtypes = [c_void_p]
+
+objc.class_getMethodImplementation.restype = c_void_p
+objc.class_getMethodImplementation.argtypes = [c_void_p, c_void_p]
+
+objc.class_getMethodImplementation_stret.restype = c_void_p
+objc.class_getMethodImplementation_stret.argtypes = [c_void_p, c_void_p]
+
+objc.class_getName.restype = c_char_p
+objc.class_getName.argtypes = [c_void_p]
+
+objc.class_getProperty.restype = c_void_p
+objc.class_getProperty.argtypes = [c_void_p, c_char_p]
+
+objc.class_getSuperclass.restype = c_void_p
+objc.class_getSuperclass.argtypes = [c_void_p]
+
+objc.class_getVersion.restype = c_int
+objc.class_getVersion.argtypes = [c_void_p]
+
+objc.class_getWeakIvarLayout.restype = c_char_p
+objc.class_getWeakIvarLayout.argtypes = [c_void_p]
+
+objc.class_isMetaClass.restype = c_bool
+objc.class_isMetaClass.argtypes = [c_void_p]
+
+objc.class_replaceMethod.restype = c_void_p
+objc.class_replaceMethod.argtypes = [c_void_p, c_void_p, c_void_p, c_char_p]
+
+objc.class_respondsToSelector.restype = c_bool
+objc.class_respondsToSelector.argtypes = [c_void_p, c_void_p]
+
+objc.class_setIvarLayout.restype = None
+objc.class_setIvarLayout.argtypes = [c_void_p, c_char_p]
+
+objc.class_setSuperclass.restype = c_void_p
+objc.class_setSuperclass.argtypes = [c_void_p, c_void_p]
+
+objc.class_setVersion.restype = None
+objc.class_setVersion.argtypes = [c_void_p, c_int]
+
+objc.class_setWeakIvarLayout.restype = None
+objc.class_setWeakIvarLayout.argtypes = [c_void_p, c_char_p]
+
+objc.ivar_getName.restype = c_char_p
+objc.ivar_getName.argtypes = [c_void_p]
+
+objc.ivar_getOffset.restype = c_ptrdiff_t
+objc.ivar_getOffset.argtypes = [c_void_p]
+
+objc.ivar_getTypeEncoding.restype = c_char_p
+objc.ivar_getTypeEncoding.argtypes = [c_void_p]
+
+objc.method_copyArgumentType.restype = c_char_p
+objc.method_copyArgumentType.argtypes = [c_void_p, c_uint]
+
+objc.method_copyReturnType.restype = c_char_p
+objc.method_copyReturnType.argtypes = [c_void_p]
+
+objc.method_exchangeImplementations.restype = None
+objc.method_exchangeImplementations.argtypes = [c_void_p, c_void_p]
+
+objc.method_getArgumentType.restype = None
+objc.method_getArgumentType.argtypes = [c_void_p, c_uint, c_char_p, c_size_t]
+
+objc.method_getImplementation.restype = c_void_p
+objc.method_getImplementation.argtypes = [c_void_p]
+
+objc.method_getName.restype = c_void_p
+objc.method_getName.argtypes = [c_void_p]
+
+objc.method_getNumberOfArguments.restype = c_uint
+objc.method_getNumberOfArguments.argtypes = [c_void_p]
+
+objc.method_getReturnType.restype = None
+objc.method_getReturnType.argtypes = [c_void_p, c_char_p, c_size_t]
+
+objc.method_getTypeEncoding.restype = c_char_p
+objc.method_getTypeEncoding.argtypes = [c_void_p]
+
+objc.method_setImplementation.restype = c_void_p
+objc.method_setImplementation.argtypes = [c_void_p, c_void_p]
+
+objc.objc_allocateClassPair.restype = c_void_p
+objc.objc_allocateClassPair.argtypes = [c_void_p, c_char_p, c_size_t]
+
+objc.objc_copyProtocolList.restype = POINTER(c_void_p)
+objc.objc_copyProtocolList.argtypes = [POINTER(c_int)]
+
+objc.objc_getAssociatedObject.restype = c_void_p
+objc.objc_getAssociatedObject.argtypes = [c_void_p, c_void_p]
+
+objc.objc_getClass.restype = c_void_p
+objc.objc_getClass.argtypes = [c_char_p]
+
+objc.objc_getClassList.restype = c_int
+objc.objc_getClassList.argtypes = [c_void_p, c_int]
+
+objc.objc_getMetaClass.restype = c_void_p
+objc.objc_getMetaClass.argtypes = [c_char_p]
+
+objc.objc_getProtocol.restype = c_void_p
+objc.objc_getProtocol.argtypes = [c_char_p]
+
+objc.objc_msgSendSuper_stret.restype = None
+
+objc.objc_msgSend_stret.restype = None
+
+objc.objc_registerClassPair.restype = None
+objc.objc_registerClassPair.argtypes = [c_void_p]
+
+objc.objc_removeAssociatedObjects.restype = None
+objc.objc_removeAssociatedObjects.argtypes = [c_void_p]
+
+objc.objc_setAssociatedObject.restype = None
+objc.objc_setAssociatedObject.argtypes = [c_void_p, c_void_p, c_void_p, c_int]
+
+objc.object_copy.restype = c_void_p
+objc.object_copy.argtypes = [c_void_p, c_size_t]
+
+objc.object_dispose.restype = c_void_p
+objc.object_dispose.argtypes = [c_void_p]
+
+objc.object_getClass.restype = c_void_p
+objc.object_getClass.argtypes = [c_void_p]
+
+objc.object_getClassName.restype = c_char_p
+objc.object_getClassName.argtypes = [c_void_p]
+
+objc.object_getInstanceVariable.restype = c_void_p
+objc.object_getInstanceVariable.argtypes = [c_void_p, c_char_p, c_void_p]
+
+objc.object_getIvar.restype = c_void_p
+objc.object_getIvar.argtypes = [c_void_p, c_void_p]
+
+objc.object_setClass.restype = c_void_p
+objc.object_setClass.argtypes = [c_void_p, c_void_p]
+
+objc.object_setInstanceVariable.restype = c_void_p
+
+objc.object_setIvar.restype = None
+objc.object_setIvar.argtypes = [c_void_p, c_void_p, c_void_p]
+
+objc.property_getAttributes.restype = c_char_p
+objc.property_getAttributes.argtypes = [c_void_p]
+
+objc.property_getName.restype = c_char_p
+objc.property_getName.argtypes = [c_void_p]
+
+objc.protocol_conformsToProtocol.restype = c_bool
+objc.protocol_conformsToProtocol.argtypes = [c_void_p, c_void_p]
+
+
+class OBJC_METHOD_DESCRIPTION(Structure):
+    _fields_ = [("name", c_void_p), ("types", c_char_p)]
+
+
+objc.protocol_copyMethodDescriptionList.restype = \
+    POINTER(OBJC_METHOD_DESCRIPTION)
+objc.protocol_copyMethodDescriptionList.argtypes = [c_void_p, c_bool,
+                                                    c_bool, POINTER(c_uint)]
+
+objc.protocol_copyPropertyList.restype = c_void_p
+objc.protocol_copyPropertyList.argtypes = [c_void_p, POINTER(c_uint)]
+
+objc.protocol_copyProtocolList = POINTER(c_void_p)
+objc.protocol_copyProtocolList.argtypes = [c_void_p, POINTER(c_uint)]
+
+objc.protocol_getMethodDescription.restype = OBJC_METHOD_DESCRIPTION
+objc.protocol_getMethodDescription.argtypes = [c_void_p, c_void_p,
+                                               c_bool, c_bool]
+
+objc.protocol_getName.restype = c_char_p
+objc.protocol_getName.argtypes = [c_void_p]
+
+objc.sel_getName.restype = c_char_p
+objc.sel_getName.argtypes = [c_void_p]
+
+objc.sel_isEqual.restype = c_bool
+objc.sel_isEqual.argtypes = [c_void_p, c_void_p]
+
+objc.sel_registerName.restype = c_void_p
+objc.sel_registerName.argtypes = [c_char_p]
+
+
+def ensure_bytes(x):
+    if isinstance(x, bytes):
+        return x
+    return x.encode('ascii')
+
+
+def get_selector(name):
+    return c_void_p(objc.sel_registerName(ensure_bytes(name)))
+
+
+def get_class(name):
+    return c_void_p(objc.objc_getClass(ensure_bytes(name)))
+
+
+def get_object_class(obj):
+    return c_void_p(objc.object_getClass(obj))
+
+
+def get_metaclass(name):
+    return c_void_p(objc.objc_getMetaClass(ensure_bytes(name)))
+
+
+def get_superclass_of_object(obj):
+    cls = c_void_p(objc.object_getClass(obj))
+    return c_void_p(objc.class_getSuperclass(cls))
+
+
+def x86_should_use_stret(restype):
+    if type(restype) != type(Structure):
+        return False
+    if not __LP64__ and sizeof(restype) <= 8:
+        return False
+    if __LP64__ and sizeof(restype) <= 16:  # maybe? I don't know?
+        return False
+    return True
+
+
+def should_use_fpret(restype):
+    if not __i386__:
+        return False
+    if __LP64__ and restype == c_longdouble:
+        return True
+    if not __LP64__ and restype in (c_float, c_double, c_longdouble):
+        return True
+    return False
+
+
+def send_message(receiver, selName, *args, **kwargs):
+    if isinstance(receiver, str):
+        receiver = get_class(receiver)
+    selector = get_selector(selName)
+    restype = kwargs.get('restype', c_void_p)
+    argtypes = kwargs.get('argtypes', [])
+    if should_use_fpret(restype):
+        objc.objc_msgSend_fpret.restype = restype
+        objc.objc_msgSend_fpret.argtypes = [c_void_p, c_void_p] + argtypes
+        result = objc.objc_msgSend_fpret(receiver, selector, *args)
+    elif x86_should_use_stret(restype):
+        objc.objc_msgSend_stret.argtypes = [POINTER(restype), c_void_p,
+                                            c_void_p] + argtypes
+        result = restype()
+        objc.objc_msgSend_stret(byref(result), receiver, selector, *args)
+    else:
+        objc.objc_msgSend.restype = restype
+        objc.objc_msgSend.argtypes = [c_void_p, c_void_p] + argtypes
+        result = objc.objc_msgSend(receiver, selector, *args)
+        if restype == c_void_p:
+            result = c_void_p(result)
+    return result
+
+
+class OBJC_SUPER(Structure):
+    _fields_ = [('receiver', c_void_p), ('class', c_void_p)]
+
+OBJC_SUPER_PTR = POINTER(OBJC_SUPER)
+
+
+def send_super(receiver, selName, *args, **kwargs):
+    if hasattr(receiver, '_as_parameter_'):
+        receiver = receiver._as_parameter_
+    superclass = get_superclass_of_object(receiver)
+    super_struct = OBJC_SUPER(receiver, superclass)
+    selector = get_selector(selName)
+    restype = kwargs.get('restype', c_void_p)
+    argtypes = kwargs.get('argtypes', None)
+    objc.objc_msgSendSuper.restype = restype
+    if argtypes:
+        objc.objc_msgSendSuper.argtypes = [OBJC_SUPER_PTR, c_void_p] + argtypes
+    else:
+        objc.objc_msgSendSuper.argtypes = None
+    result = objc.objc_msgSendSuper(byref(super_struct), selector, *args)
+    if restype == c_void_p:
+        result = c_void_p(result)
+    return result
+
+
+cfunctype_table = {}
+
+
+def parse_type_encoding(encoding):
+    type_encodings = []
+    brace_count = 0    # number of unclosed curly braces
+    bracket_count = 0  # number of unclosed square brackets
+    typecode = b''
+    for c in encoding:
+        if isinstance(c, int):
+            c = bytes([c])
+
+        if c == b'{':
+            if typecode and typecode[-1:] != b'^' and brace_count == 0 and \
+                    bracket_count == 0:
+                type_encodings.append(typecode)
+                typecode = b''
+            typecode += c
+            brace_count += 1
+        elif c == b'}':
+            typecode += c
+            brace_count -= 1
+            assert(brace_count >= 0)
+        elif c == b'[':
+            if typecode and typecode[-1:] != b'^' and brace_count == 0 and \
+                    bracket_count == 0:
+                type_encodings.append(typecode)
+                typecode = b''
+            typecode += c
+            bracket_count += 1
+        elif c == b']':
+            typecode += c
+            bracket_count -= 1
+            assert(bracket_count >= 0)
+        elif brace_count or bracket_count:
+            typecode += c
+        elif c in b'0123456789':
+            pass
+        elif c in b'rnNoORV':
+            pass
+        elif c in b'^cislqCISLQfdBv*@#:b?':
+            if typecode and typecode[-1:] == b'^':
+                typecode += c
+            else:
+                if typecode:
+                    type_encodings.append(typecode)
+                typecode = c
+
+    if typecode:
+        type_encodings.append(typecode)
+
+    return type_encodings
+
+
+def cfunctype_for_encoding(encoding):
+    if encoding in cfunctype_table:
+        return cfunctype_table[encoding]
+    typecodes = {b'c': c_char, b'i': c_int, b's': c_short, b'l': c_long,
+                 b'q': c_longlong, b'C': c_ubyte, b'I': c_uint, b'S': c_ushort,
+                 b'L': c_ulong, b'Q': c_ulonglong, b'f': c_float,
+                 b'd': c_double, b'B': c_bool, b'v': None, b'*': c_char_p,
+                 b'@': c_void_p, b'#': c_void_p, b':': c_void_p,
+                 NSPointEncoding: NSPoint, NSSizeEncoding: NSSize,
+                 NSRectEncoding: NSRect, NSRangeEncoding: NSRange,
+                 PyObjectEncoding: py_object}
+    argtypes = []
+    for code in parse_type_encoding(encoding):
+        if code in typecodes:
+            argtypes.append(typecodes[code])
+        elif code[0:1] == b'^' and code[1:] in typecodes:
+            argtypes.append(POINTER(typecodes[code[1:]]))
+        else:
+            raise Exception('unknown type encoding: ' + code)
+
+    cfunctype = CFUNCTYPE(*argtypes)
+    cfunctype_table[encoding] = cfunctype
+    return cfunctype
+
+
+def create_subclass(superclass, name):
+    if isinstance(superclass, str):
+        superclass = get_class(superclass)
+    return c_void_p(objc.objc_allocateClassPair(superclass,
+                                                ensure_bytes(name), 0))
+
+
+def register_subclass(subclass):
+    objc.objc_registerClassPair(subclass)
+
+
+def add_method(cls, selName, method, types):
+    type_encodings = parse_type_encoding(types)
+    assert(type_encodings[1] == b'@')  # ensure id self typecode
+    assert(type_encodings[2] == b':')  # ensure SEL cmd typecode
+    selector = get_selector(selName)
+    cfunctype = cfunctype_for_encoding(types)
+    imp = cfunctype(method)
+    objc.class_addMethod.argtypes = [c_void_p, c_void_p, cfunctype, c_char_p]
+    objc.class_addMethod(cls, selector, imp, types)
+    return imp
+
+
+def add_ivar(cls, name, vartype):
+    return objc.class_addIvar(cls, ensure_bytes(name), sizeof(vartype),
+                              alignment(vartype), encoding_for_ctype(vartype))
+
+
+def set_instance_variable(obj, varname, value, vartype):
+    objc.object_setInstanceVariable.argtypes = [c_void_p, c_char_p, vartype]
+    objc.object_setInstanceVariable(obj, ensure_bytes(varname), value)
+
+
+def get_instance_variable(obj, varname, vartype):
+    variable = vartype()
+    objc.object_getInstanceVariable(obj, ensure_bytes(varname),
+                                    byref(variable))
+    return variable.value
+
+
+class ObjCMethod(object):
+    """This represents an unbound Objective-C method (really an IMP)."""
+    typecodes = {b'c': c_byte, b'i': c_int, b's': c_short, b'l': c_long,
+                 b'q': c_longlong, b'C': c_ubyte, b'I': c_uint, b'S': c_ushort,
+                 b'L': c_ulong, b'Q': c_ulonglong, b'f': c_float,
+                 b'd': c_double, b'B': c_bool, b'v': None, b'Vv': None,
+                 b'*': c_char_p, b'@': c_void_p, b'#': c_void_p,
+                 b':': c_void_p, b'^v': c_void_p, b'?': c_void_p,
+                 NSPointEncoding: NSPoint, NSSizeEncoding: NSSize,
+                 NSRectEncoding: NSRect, NSRangeEncoding: NSRange,
+                 PyObjectEncoding: py_object}
+    cfunctype_table = {}
+
+    def __init__(self, method):
+        self.selector = c_void_p(objc.method_getName(method))
+        self.name = objc.sel_getName(self.selector)
+        self.pyname = self.name.replace(b':', b'_')
+        self.encoding = objc.method_getTypeEncoding(method)
+        self.return_type = objc.method_copyReturnType(method)
+        self.nargs = objc.method_getNumberOfArguments(method)
+        self.imp = c_void_p(objc.method_getImplementation(method))
+        self.argument_types = []
+        for i in range(self.nargs):
+            buffer = c_buffer(512)
+            objc.method_getArgumentType(method, i, buffer, len(buffer))
+            self.argument_types.append(buffer.value)
+        try:
+            self.argtypes = [self.ctype_for_encoding(t)
+                             for t in self.argument_types]
+        except:
+            self.argtypes = None
+        try:
+            if self.return_type == b'@':
+                self.restype = ObjCInstance
+            elif self.return_type == b'#':
+                self.restype = ObjCClass
+            else:
+                self.restype = self.ctype_for_encoding(self.return_type)
+        except:
+            self.restype = None
+        self.func = None
+
+    def ctype_for_encoding(self, encoding):
+        """Return ctypes type for an encoded Objective-C type."""
+        if encoding in self.typecodes:
+            return self.typecodes[encoding]
+        elif encoding[0:1] == b'^' and encoding[1:] in self.typecodes:
+            return POINTER(self.typecodes[encoding[1:]])
+        elif encoding[0:1] == b'^' and encoding[1:] in [CGImageEncoding,
+                                                        NSZoneEncoding]:
+            return c_void_p
+        elif encoding[0:1] == b'r' and encoding[1:] in self.typecodes:
+            return self.typecodes[encoding[1:]]
+        elif encoding[0:2] == b'r^' and encoding[2:] in self.typecodes:
+            return POINTER(self.typecodes[encoding[2:]])
+        else:
+            raise Exception('unknown encoding for %s: %s'
+                            % (self.name, encoding))
+
+    def get_prototype(self):
+        if self.restype == ObjCInstance or self.restype == ObjCClass:
+            self.prototype = CFUNCTYPE(c_void_p, *self.argtypes)
+        else:
+            self.prototype = CFUNCTYPE(self.restype, *self.argtypes)
+        return self.prototype
+
+    def __repr__(self):
+        return "<ObjCMethod: %s %s>" % (self.name, self.encoding)
+
+    def get_callable(self):
+        if not self.func:
+            prototype = self.get_prototype()
+            self.func = cast(self.imp, prototype)
+            if self.restype == ObjCInstance or self.restype == ObjCClass:
+                self.func.restype = c_void_p
+            else:
+                self.func.restype = self.restype
+            self.func.argtypes = self.argtypes
+        return self.func
+
+    def __call__(self, objc_id, *args):
+        f = self.get_callable()
+        try:
+            result = f(objc_id, self.selector, *args)
+            if self.restype == ObjCInstance:
+                result = ObjCInstance(result)
+            elif self.restype == ObjCClass:
+                result = ObjCClass(result)
+            return result
+        except ArgumentError as error:
+            error.args += ('selector = ' + self.name,
+                           'argtypes =' + str(self.argtypes),
+                           'encoding = ' + self.encoding)
+            raise
+
+
+class ObjCBoundMethod(object):
+    def __init__(self, method, objc_id):
+        self.method = method
+        self.objc_id = objc_id
+
+    def __repr__(self):
+        return '<ObjCBoundMethod %s (%s)>' % (self.method.name, self.objc_id)
+
+    def __call__(self, *args):
+        return self.method(self.objc_id, *args)
+
+
+class ObjCClass(object):
+    _registered_classes = {}
+
+    def __new__(cls, class_name_or_ptr):
+        if isinstance(class_name_or_ptr, str):
+            name = class_name_or_ptr
+            ptr = get_class(name)
+        else:
+            ptr = class_name_or_ptr
+            if not isinstance(ptr, c_void_p):
+                ptr = c_void_p(ptr)
+            name = objc.class_getName(ptr)
+
+        if name in cls._registered_classes:
+            return cls._registered_classes[name]
+
+        objc_class = super(ObjCClass, cls).__new__(cls)
+        objc_class.ptr = ptr
+        objc_class.name = name
+        objc_class.instance_methods = {}   # mapping of name -> instance method
+        objc_class.class_methods = {}      # mapping of name -> class method
+        objc_class._as_parameter_ = ptr    # for ctypes argument passing
+
+        cls._registered_classes[name] = objc_class
+        objc_class.cache_instance_methods()
+        objc_class.cache_class_methods()
+        return objc_class
+
+    def __repr__(self):
+        return "<ObjCClass: %s at %s>" % (self.name, str(self.ptr.value))
+
+    def cache_instance_methods(self):
+        count = c_uint()
+        method_array = objc.class_copyMethodList(self.ptr, byref(count))
+        for i in range(count.value):
+            method = c_void_p(method_array[i])
+            objc_method = ObjCMethod(method)
+            self.instance_methods[objc_method.pyname] = objc_method
+
+    def cache_class_methods(self):
+        count = c_uint()
+        args = [objc.object_getClass(self.ptr), byref(count)]
+        method_array = objc.class_copyMethodList(*args)
+        for i in range(count.value):
+            method = c_void_p(method_array[i])
+            objc_method = ObjCMethod(method)
+            self.class_methods[objc_method.pyname] = objc_method
+
+    def get_instance_method(self, name):
+        if name in self.instance_methods:
+            return self.instance_methods[name]
+        else:
+            selector = get_selector(name.replace(b'_', b':'))
+            method = c_void_p(objc.class_getInstanceMethod(self.ptr, selector))
+            if method.value:
+                objc_method = ObjCMethod(method)
+                self.instance_methods[name] = objc_method
+                return objc_method
+        return None
+
+    def get_class_method(self, name):
+        if name in self.class_methods:
+            return self.class_methods[name]
+        else:
+            selector = get_selector(name.replace(b'_', b':'))
+            method = c_void_p(objc.class_getClassMethod(self.ptr, selector))
+            if method.value:
+                objc_method = ObjCMethod(method)
+                self.class_methods[name] = objc_method
+                return objc_method
+        return None
+
+    def __getattr__(self, name):
+        name = ensure_bytes(name)
+        method = self.get_class_method(name)
+        if method:
+            return ObjCBoundMethod(method, self.ptr)
+        method = self.get_instance_method(name)
+        if method:
+            return method
+        raise AttributeError('ObjCClass %s has no attribute %s'
+                             % (self.name, name))
+
+
+class ObjCInstance(object):
+    _cached_objects = {}
+
+    def __new__(cls, object_ptr):
+        if not isinstance(object_ptr, c_void_p):
+            object_ptr = c_void_p(object_ptr)
+        if not object_ptr.value:
+            return None
+        if object_ptr.value in cls._cached_objects:
+            return cls._cached_objects[object_ptr.value]
+
+        objc_instance = super(ObjCInstance, cls).__new__(cls)
+        objc_instance.ptr = object_ptr
+        objc_instance._as_parameter_ = object_ptr
+        class_ptr = c_void_p(objc.object_getClass(object_ptr))
+        objc_instance.objc_class = ObjCClass(class_ptr)
+
+        cls._cached_objects[object_ptr.value] = objc_instance
+        observer = send_message(send_message('DeallocationObserver', 'alloc'),
+                                'initWithObject:', objc_instance)
+        objc.objc_setAssociatedObject(objc_instance, observer, observer, 0x301)
+        send_message(observer, 'release')
+        return objc_instance
+
+    def __repr__(self):
+        if self.objc_class.name == b'NSCFString':
+            from .cocoalibs import cfstring_to_string
+            string = cfstring_to_string(self)
+            return ("<ObjCInstance %#x: %s (%s) at %s>"
+                    % (id(self), self.objc_class.name, string,
+                       str(self.ptr.value)))
+        return ("<ObjCInstance %#x: %s at %s>"
+                % (id(self), self.objc_class.name, str(self.ptr.value)))
+
+    def __getattr__(self, name):
+        name = ensure_bytes(name)
+        method = self.objc_class.get_instance_method(name)
+        if method:
+            return ObjCBoundMethod(method, self)
+        method = self.objc_class.get_class_method(name)
+        if method:
+            return ObjCBoundMethod(method, self.objc_class.ptr)
+        keys = list(self.objc_class.instance_methods.keys())
+        raise AttributeError('ObjCInstance %s has no attribute %s, only:\n%s'
+                             % (self.objc_class.name, name, keys))
+
+
+def convert_method_arguments(encoding, args):
+    new_args = []
+    arg_encodings = parse_type_encoding(encoding)[3:]
+    for e, a in zip(arg_encodings, args):
+        if e == b'@':
+            new_args.append(ObjCInstance(a))
+        elif e == b'#':
+            new_args.append(ObjCClass(a))
+        else:
+            new_args.append(a)
+    return new_args
+
+
+class ObjCSubclass(object):
+
+    def __init__(self, superclass, name, register=True):
+        self._imp_table = {}
+        self.name = name
+        self.objc_cls = create_subclass(superclass, name)
+        self._as_parameter_ = self.objc_cls
+        if register:
+            self.register()
+
+    def register(self):
+        objc.objc_registerClassPair(self.objc_cls)
+        self.objc_metaclass = get_metaclass(self.name)
+
+    def add_ivar(self, varname, vartype):
+        return add_ivar(self.objc_cls, varname, vartype)
+
+    def add_method(self, method, name, encoding):
+        imp = add_method(self.objc_cls, name, method, encoding)
+        self._imp_table[name] = imp
+
+    def add_class_method(self, method, name, encoding):
+        imp = add_method(self.objc_metaclass, name, method, encoding)
+        self._imp_table[name] = imp
+
+    def rawmethod(self, encoding):
+        encoding = ensure_bytes(encoding)
+        typecodes = parse_type_encoding(encoding)
+        typecodes.insert(1, b'@:')
+        encoding = b''.join(typecodes)
+
+        def decorator(f):
+            name = f.__name__.replace('_', ':')
+            self.add_method(f, name, encoding)
+            return f
+        return decorator
+
+    def method(self, encoding):
+        encoding = ensure_bytes(encoding)
+        typecodes = parse_type_encoding(encoding)
+        typecodes.insert(1, b'@:')
+        encoding = b''.join(typecodes)
+
+        def decorator(f):
+            def objc_method(objc_self, objc_cmd, *args):
+                py_self = ObjCInstance(objc_self)
+                py_self.objc_cmd = objc_cmd
+                args = convert_method_arguments(encoding, args)
+                result = f(py_self, *args)
+                if isinstance(result, ObjCClass):
+                    result = result.ptr.value
+                elif isinstance(result, ObjCInstance):
+                    result = result.ptr.value
+                return result
+            name = f.__name__.replace('_', ':')
+            self.add_method(objc_method, name, encoding)
+            return objc_method
+        return decorator
+
+    def classmethod(self, encoding):
+        """Function decorator for class methods."""
+        # Add encodings for hidden self and cmd arguments.
+        encoding = ensure_bytes(encoding)
+        typecodes = parse_type_encoding(encoding)
+        typecodes.insert(1, b'@:')
+        encoding = b''.join(typecodes)
+
+        def decorator(f):
+            def objc_class_method(objc_cls, objc_cmd, *args):
+                py_cls = ObjCClass(objc_cls)
+                py_cls.objc_cmd = objc_cmd
+                args = convert_method_arguments(encoding, args)
+                result = f(py_cls, *args)
+                if isinstance(result, ObjCClass):
+                    result = result.ptr.value
+                elif isinstance(result, ObjCInstance):
+                    result = result.ptr.value
+                return result
+            name = f.__name__.replace('_', ':')
+            self.add_class_method(objc_class_method, name, encoding)
+            return objc_class_method
+        return decorator
+
+
+# XXX This causes segfaults in all backends (yikes!), and makes it so that
+# pyglet can't even be loaded. We'll just have to live with leaks for now,
+# which is probably alright since we only use the
+# NSFontManager.sharedFontManager class currently.
+
+# class DeallocationObserver_Implementation(object):
+#    DeallocationObserver = ObjCSubclass('NSObject', 'DeallocationObserver',
+#                                        register=False)
+#    DeallocationObserver.add_ivar('observed_object', c_void_p)
+#    DeallocationObserver.register()
+#
+#    @DeallocationObserver.rawmethod('@@')
+#    def initWithObject_(self, cmd, anObject):
+#        self = send_super(self, 'init')
+#        self = self.value
+#        set_instance_variable(self, 'observed_object', anObject, c_void_p)
+#        return self
+#
+#    @DeallocationObserver.rawmethod('v')
+#    def dealloc(self, cmd):
+#        anObject = get_instance_variable(self, 'observed_object', c_void_p)
+#        ObjCInstance._cached_objects.pop(anObject, None)
+#        send_super(self, 'dealloc')
+#
+#    @DeallocationObserver.rawmethod('v')
+#    def finalize(self, cmd):
+#        anObject = get_instance_variable(self, 'observed_object', c_void_p)
+#        ObjCInstance._cached_objects.pop(anObject, None)
+#        send_super(self, 'finalize')
+
+
+##############################################################################
+# cocoalibs.py
+
+cf = cdll.LoadLibrary(util.find_library('CoreFoundation'))
+
+kCFStringEncodingUTF8 = 0x08000100
+
+CFAllocatorRef = c_void_p
+CFStringEncoding = c_uint32
+
+cf.CFStringCreateWithCString.restype = c_void_p
+cf.CFStringCreateWithCString.argtypes = [CFAllocatorRef, c_char_p,
+                                         CFStringEncoding]
+
+cf.CFRelease.restype = c_void_p
+cf.CFRelease.argtypes = [c_void_p]
+
+cf.CFStringGetLength.restype = CFIndex
+cf.CFStringGetLength.argtypes = [c_void_p]
+
+cf.CFStringGetMaximumSizeForEncoding.restype = CFIndex
+cf.CFStringGetMaximumSizeForEncoding.argtypes = [CFIndex, CFStringEncoding]
+
+cf.CFStringGetCString.restype = c_bool
+cf.CFStringGetCString.argtypes = [c_void_p, c_char_p, CFIndex,
+                                  CFStringEncoding]
+
+cf.CFStringGetTypeID.restype = CFTypeID
+cf.CFStringGetTypeID.argtypes = []
+
+cf.CFAttributedStringCreate.restype = c_void_p
+cf.CFAttributedStringCreate.argtypes = [CFAllocatorRef, c_void_p, c_void_p]
+
+cf.CFURLCreateWithFileSystemPath.restype = c_void_p
+cf.CFURLCreateWithFileSystemPath.argtypes = [CFAllocatorRef, c_void_p,
+                                             CFIndex, c_bool]
+
+
+def CFSTR(string):
+    args = [None, string.encode('utf8'), kCFStringEncodingUTF8]
+    return ObjCInstance(c_void_p(cf.CFStringCreateWithCString(*args)))
+
+
+def get_NSString(string):
+    """Autoreleased version of CFSTR"""
+    return CFSTR(string).autorelease()
+
+
+def cfstring_to_string(cfstring):
+    length = cf.CFStringGetLength(cfstring)
+    size = cf.CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8)
+    buffer = c_buffer(size + 1)
+    result = cf.CFStringGetCString(cfstring, buffer, len(buffer),
+                                   kCFStringEncodingUTF8)
+    if result:
+        return buffer.value.decode('utf-8')
+
+
+cf.CFDataCreate.restype = c_void_p
+cf.CFDataCreate.argtypes = [c_void_p, c_void_p, CFIndex]
+
+cf.CFDataGetBytes.restype = None
+cf.CFDataGetBytes.argtypes = [c_void_p, CFRange, c_void_p]
+
+cf.CFDataGetLength.restype = CFIndex
+cf.CFDataGetLength.argtypes = [c_void_p]
+
+cf.CFDictionaryGetValue.restype = c_void_p
+cf.CFDictionaryGetValue.argtypes = [c_void_p, c_void_p]
+
+cf.CFDictionaryAddValue.restype = None
+cf.CFDictionaryAddValue.argtypes = [c_void_p, c_void_p, c_void_p]
+
+cf.CFDictionaryCreateMutable.restype = c_void_p
+cf.CFDictionaryCreateMutable.argtypes = [CFAllocatorRef, CFIndex,
+                                         c_void_p, c_void_p]
+
+cf.CFNumberCreate.restype = c_void_p
+cf.CFNumberCreate.argtypes = [CFAllocatorRef, CFNumberType, c_void_p]
+
+cf.CFNumberGetType.restype = CFNumberType
+cf.CFNumberGetType.argtypes = [c_void_p]
+
+cf.CFNumberGetValue.restype = c_ubyte
+cf.CFNumberGetValue.argtypes = [c_void_p, CFNumberType, c_void_p]
+
+cf.CFNumberGetTypeID.restype = CFTypeID
+cf.CFNumberGetTypeID.argtypes = []
+
+cf.CFGetTypeID.restype = CFTypeID
+cf.CFGetTypeID.argtypes = [c_void_p]
+
+# CFNumber.h
+kCFNumberSInt8Type = 1
+kCFNumberSInt16Type = 2
+kCFNumberSInt32Type = 3
+kCFNumberSInt64Type = 4
+kCFNumberFloat32Type = 5
+kCFNumberFloat64Type = 6
+kCFNumberCharType = 7
+kCFNumberShortType = 8
+kCFNumberIntType = 9
+kCFNumberLongType = 10
+kCFNumberLongLongType = 11
+kCFNumberFloatType = 12
+kCFNumberDoubleType = 13
+kCFNumberCFIndexType = 14
+kCFNumberNSIntegerType = 15
+kCFNumberCGFloatType = 16
+kCFNumberMaxType = 16
+
+
+def cfnumber_to_number(cfnumber):
+    """Convert CFNumber to python int or float."""
+    numeric_type = cf.CFNumberGetType(cfnumber)
+    cfnum_to_ctype = {kCFNumberSInt8Type: c_int8, kCFNumberSInt16Type: c_int16,
+                      kCFNumberSInt32Type: c_int32,
+                      kCFNumberSInt64Type: c_int64,
+                      kCFNumberFloat32Type: c_float,
+                      kCFNumberFloat64Type: c_double,
+                      kCFNumberCharType: c_byte, kCFNumberShortType: c_short,
+                      kCFNumberIntType: c_int, kCFNumberLongType: c_long,
+                      kCFNumberLongLongType: c_longlong,
+                      kCFNumberFloatType: c_float,
+                      kCFNumberDoubleType: c_double,
+                      kCFNumberCFIndexType: CFIndex,
+                      kCFNumberCGFloatType: CGFloat}
+
+    if numeric_type in cfnum_to_ctype:
+        t = cfnum_to_ctype[numeric_type]
+        result = t()
+        if cf.CFNumberGetValue(cfnumber, numeric_type, byref(result)):
+            return result.value
+    else:
+        raise Exception(
+            'cfnumber_to_number: unhandled CFNumber type %d' % numeric_type)
+
+# Dictionary of cftypes matched to the method converting them to python values.
+known_cftypes = {cf.CFStringGetTypeID(): cfstring_to_string,
+                 cf.CFNumberGetTypeID(): cfnumber_to_number}
+
+
+def cftype_to_value(cftype):
+    """Convert a CFType into an equivalent python type.
+    The convertible CFTypes are taken from the known_cftypes
+    dictionary, which may be added to if another library implements
+    its own conversion methods."""
+    if not cftype:
+        return None
+    typeID = cf.CFGetTypeID(cftype)
+    if typeID in known_cftypes:
+        convert_function = known_cftypes[typeID]
+        return convert_function(cftype)
+    else:
+        return cftype
+
+cf.CFSetGetCount.restype = CFIndex
+cf.CFSetGetCount.argtypes = [c_void_p]
+
+cf.CFSetGetValues.restype = None
+# PyPy 1.7 is fine with 2nd arg as POINTER(c_void_p),
+# but CPython ctypes 1.1.0 complains, so just use c_void_p.
+cf.CFSetGetValues.argtypes = [c_void_p, c_void_p]
+
+
+def cfset_to_set(cfset):
+    """Convert CFSet to python set."""
+    count = cf.CFSetGetCount(cfset)
+    buffer = (c_void_p * count)()
+    cf.CFSetGetValues(cfset, byref(buffer))
+    return set([cftype_to_value(c_void_p(buffer[i])) for i in range(count)])
+
+cf.CFArrayGetCount.restype = CFIndex
+cf.CFArrayGetCount.argtypes = [c_void_p]
+
+cf.CFArrayGetValueAtIndex.restype = c_void_p
+cf.CFArrayGetValueAtIndex.argtypes = [c_void_p, CFIndex]
+
+
+def cfarray_to_list(cfarray):
+    """Convert CFArray to python list."""
+    count = cf.CFArrayGetCount(cfarray)
+    return [cftype_to_value(c_void_p(cf.CFArrayGetValueAtIndex(cfarray, i)))
+            for i in range(count)]
+
+
+kCFRunLoopDefaultMode = c_void_p.in_dll(cf, 'kCFRunLoopDefaultMode')
+
+cf.CFRunLoopGetCurrent.restype = c_void_p
+cf.CFRunLoopGetCurrent.argtypes = []
+
+cf.CFRunLoopGetMain.restype = c_void_p
+cf.CFRunLoopGetMain.argtypes = []
+
+cf.CFShow.restype = None
+cf.CFShow.argtypes = [c_void_p]
+
+######################################################################
+
+# APPLICATION KIT
+
+# Even though we don't use this directly, it must be loaded so that
+# we can find the NSApplication, NSWindow, and NSView classes.
+appkit = cdll.LoadLibrary(util.find_library('AppKit'))
+
+NSDefaultRunLoopMode = c_void_p.in_dll(appkit, 'NSDefaultRunLoopMode')
+NSEventTrackingRunLoopMode = c_void_p.in_dll(
+    appkit, 'NSEventTrackingRunLoopMode')
+NSApplicationDidHideNotification = c_void_p.in_dll(
+    appkit, 'NSApplicationDidHideNotification')
+NSApplicationDidUnhideNotification = c_void_p.in_dll(
+    appkit, 'NSApplicationDidUnhideNotification')
+
+# /System/Library/Frameworks/AppKit.framework/Headers/NSEvent.h
+# NSAnyEventMask = 0xFFFFFFFFL     # NSUIntegerMax
+# Commented out b/c not Py3k compatible
+
+NSKeyDown = 10
+NSKeyUp = 11
+NSFlagsChanged = 12
+NSApplicationDefined = 15
+
+NSAlphaShiftKeyMask = 1 << 16
+NSShiftKeyMask = 1 << 17
+NSControlKeyMask = 1 << 18
+NSAlternateKeyMask = 1 << 19
+NSCommandKeyMask = 1 << 20
+NSNumericPadKeyMask = 1 << 21
+NSHelpKeyMask = 1 << 22
+NSFunctionKeyMask = 1 << 23
+
+NSInsertFunctionKey = 0xF727
+NSDeleteFunctionKey = 0xF728
+NSHomeFunctionKey = 0xF729
+NSBeginFunctionKey = 0xF72A
+NSEndFunctionKey = 0xF72B
+NSPageUpFunctionKey = 0xF72C
+NSPageDownFunctionKey = 0xF72D
+
+# /System/Library/Frameworks/AppKit.framework/Headers/NSWindow.h
+NSBorderlessWindowMask = 0
+NSTitledWindowMask = 1 << 0
+NSClosableWindowMask = 1 << 1
+NSMiniaturizableWindowMask = 1 << 2
+NSResizableWindowMask = 1 << 3
+
+# /System/Library/Frameworks/AppKit.framework/Headers/NSPanel.h
+NSUtilityWindowMask = 1 << 4
+
+# /System/Library/Frameworks/AppKit.framework/Headers/NSGraphics.h
+NSBackingStoreRetained = 0
+NSBackingStoreNonretained = 1
+NSBackingStoreBuffered = 2
+
+# /System/Library/Frameworks/AppKit.framework/Headers/NSTrackingArea.h
+NSTrackingMouseEnteredAndExited = 0x01
+NSTrackingMouseMoved = 0x02
+NSTrackingCursorUpdate = 0x04
+NSTrackingActiveInActiveApp = 0x40
+
+# /System/Library/Frameworks/AppKit.framework/Headers/NSOpenGL.h
+NSOpenGLPFAAllRenderers = 1   # choose from all available renderers
+NSOpenGLPFADoubleBuffer = 5   # choose a double buffered pixel format
+NSOpenGLPFAStereo = 6   # stereo buffering supported
+NSOpenGLPFAAuxBuffers = 7   # number of aux buffers
+NSOpenGLPFAColorSize = 8   # number of color buffer bits
+NSOpenGLPFAAlphaSize = 11   # number of alpha component bits
+NSOpenGLPFADepthSize = 12   # number of depth buffer bits
+NSOpenGLPFAStencilSize = 13   # number of stencil buffer bits
+NSOpenGLPFAAccumSize = 14   # number of accum buffer bits
+NSOpenGLPFAMinimumPolicy = 51   # never choose smaller buffers than requested
+NSOpenGLPFAMaximumPolicy = 52   # choose largest buffers of type requested
+NSOpenGLPFAOffScreen = 53   # choose an off-screen capable renderer
+NSOpenGLPFAFullScreen = 54   # choose a full-screen capable renderer
+NSOpenGLPFASampleBuffers = 55   # number of multi sample buffers
+NSOpenGLPFASamples = 56   # number of samples per multi sample buffer
+NSOpenGLPFAAuxDepthStencil = 57   # each aux buffer has its own depth stencil
+NSOpenGLPFAColorFloat = 58   # color buffers store floating point pixels
+NSOpenGLPFAMultisample = 59   # choose multisampling
+NSOpenGLPFASupersample = 60   # choose supersampling
+NSOpenGLPFASampleAlpha = 61   # request alpha filtering
+NSOpenGLPFARendererID = 70   # request renderer by ID
+NSOpenGLPFASingleRenderer = 71   # choose a single renderer for all screens
+NSOpenGLPFANoRecovery = 72   # disable all failure recovery systems
+NSOpenGLPFAAccelerated = 73   # choose a hardware accelerated renderer
+NSOpenGLPFAClosestPolicy = 74   # choose the closest color buffer to request
+NSOpenGLPFARobust = 75   # renderer does not need failure recovery
+NSOpenGLPFABackingStore = 76   # back buffer contents are valid after swap
+NSOpenGLPFAMPSafe = 78   # renderer is multi-processor safe
+NSOpenGLPFAWindow = 80   # can be used to render to an onscreen window
+NSOpenGLPFAMultiScreen = 81   # single window can span multiple screens
+NSOpenGLPFACompliant = 83   # renderer is opengl compliant
+NSOpenGLPFAScreenMask = 84   # bit mask of supported physical screens
+NSOpenGLPFAPixelBuffer = 90   # can be used to render to a pbuffer
+# can be used to render offline to a pbuffer
+NSOpenGLPFARemotePixelBuffer = 91
+NSOpenGLPFAAllowOfflineRenderers = 96  # allow use of offline renderers
+# choose a hardware accelerated compute device
+NSOpenGLPFAAcceleratedCompute = 97
+# number of virtual screens in this format
+NSOpenGLPFAVirtualScreenCount = 128
+
+NSOpenGLCPSwapInterval = 222
+
+
+# /System/Library/Frameworks/ApplicationServices.framework/Frameworks/...
+#     CoreGraphics.framework/Headers/CGImage.h
+kCGImageAlphaNone = 0
+kCGImageAlphaPremultipliedLast = 1
+kCGImageAlphaPremultipliedFirst = 2
+kCGImageAlphaLast = 3
+kCGImageAlphaFirst = 4
+kCGImageAlphaNoneSkipLast = 5
+kCGImageAlphaNoneSkipFirst = 6
+kCGImageAlphaOnly = 7
+
+kCGImageAlphaPremultipliedLast = 1
+
+kCGBitmapAlphaInfoMask = 0x1F
+kCGBitmapFloatComponents = 1 << 8
+
+kCGBitmapByteOrderMask = 0x7000
+kCGBitmapByteOrderDefault = 0 << 12
+kCGBitmapByteOrder16Little = 1 << 12
+kCGBitmapByteOrder32Little = 2 << 12
+kCGBitmapByteOrder16Big = 3 << 12
+kCGBitmapByteOrder32Big = 4 << 12
+
+# NSApplication.h
+NSApplicationPresentationDefault = 0
+NSApplicationPresentationHideDock = 1 << 1
+NSApplicationPresentationHideMenuBar = 1 << 3
+NSApplicationPresentationDisableProcessSwitching = 1 << 5
+NSApplicationPresentationDisableHideApplication = 1 << 8
+
+# NSRunningApplication.h
+NSApplicationActivationPolicyRegular = 0
+NSApplicationActivationPolicyAccessory = 1
+NSApplicationActivationPolicyProhibited = 2
+
+######################################################################
+
+# QUARTZ / COREGRAPHICS
+
+quartz = cdll.LoadLibrary(util.find_library('quartz'))
+
+CGDirectDisplayID = c_uint32     # CGDirectDisplay.h
+CGError = c_int32                # CGError.h
+CGBitmapInfo = c_uint32          # CGImage.h
+
+# /System/Library/Frameworks/ApplicationServices.framework/Frameworks/...
+#     ImageIO.framework/Headers/CGImageProperties.h
+kCGImagePropertyGIFDictionary = c_void_p.in_dll(
+    quartz, 'kCGImagePropertyGIFDictionary')
+kCGImagePropertyGIFDelayTime = c_void_p.in_dll(
+    quartz, 'kCGImagePropertyGIFDelayTime')
+
+# /System/Library/Frameworks/ApplicationServices.framework/Frameworks/...
+#     CoreGraphics.framework/Headers/CGColorSpace.h
+kCGRenderingIntentDefault = 0
+
+quartz.CGDisplayIDToOpenGLDisplayMask.restype = c_uint32
+quartz.CGDisplayIDToOpenGLDisplayMask.argtypes = [c_uint32]
+
+quartz.CGMainDisplayID.restype = CGDirectDisplayID
+quartz.CGMainDisplayID.argtypes = []
+
+quartz.CGShieldingWindowLevel.restype = c_int32
+quartz.CGShieldingWindowLevel.argtypes = []
+
+quartz.CGCursorIsVisible.restype = c_bool
+
+quartz.CGDisplayCopyAllDisplayModes.restype = c_void_p
+quartz.CGDisplayCopyAllDisplayModes.argtypes = [CGDirectDisplayID, c_void_p]
+
+quartz.CGDisplaySetDisplayMode.restype = CGError
+quartz.CGDisplaySetDisplayMode.argtypes = [
+    CGDirectDisplayID, c_void_p, c_void_p]
+
+quartz.CGDisplayCapture.restype = CGError
+quartz.CGDisplayCapture.argtypes = [CGDirectDisplayID]
+
+quartz.CGDisplayRelease.restype = CGError
+quartz.CGDisplayRelease.argtypes = [CGDirectDisplayID]
+
+quartz.CGDisplayCopyDisplayMode.restype = c_void_p
+quartz.CGDisplayCopyDisplayMode.argtypes = [CGDirectDisplayID]
+
+quartz.CGDisplayModeGetRefreshRate.restype = c_double
+quartz.CGDisplayModeGetRefreshRate.argtypes = [c_void_p]
+
+quartz.CGDisplayModeRetain.restype = c_void_p
+quartz.CGDisplayModeRetain.argtypes = [c_void_p]
+
+quartz.CGDisplayModeRelease.restype = None
+quartz.CGDisplayModeRelease.argtypes = [c_void_p]
+
+quartz.CGDisplayModeGetWidth.restype = c_size_t
+quartz.CGDisplayModeGetWidth.argtypes = [c_void_p]
+
+quartz.CGDisplayModeGetHeight.restype = c_size_t
+quartz.CGDisplayModeGetHeight.argtypes = [c_void_p]
+
+quartz.CGDisplayModeCopyPixelEncoding.restype = c_void_p
+quartz.CGDisplayModeCopyPixelEncoding.argtypes = [c_void_p]
+
+quartz.CGGetActiveDisplayList.restype = CGError
+quartz.CGGetActiveDisplayList.argtypes = [
+    c_uint32, POINTER(CGDirectDisplayID), POINTER(c_uint32)]
+
+quartz.CGDisplayBounds.restype = CGRect
+quartz.CGDisplayBounds.argtypes = [CGDirectDisplayID]
+
+quartz.CGImageSourceCreateWithData.restype = c_void_p
+quartz.CGImageSourceCreateWithData.argtypes = [c_void_p, c_void_p]
+
+quartz.CGImageSourceCreateImageAtIndex.restype = c_void_p
+quartz.CGImageSourceCreateImageAtIndex.argtypes = [
+    c_void_p, c_size_t, c_void_p]
+
+quartz.CGImageSourceCopyPropertiesAtIndex.restype = c_void_p
+quartz.CGImageSourceCopyPropertiesAtIndex.argtypes = [
+    c_void_p, c_size_t, c_void_p]
+
+quartz.CGImageGetDataProvider.restype = c_void_p
+quartz.CGImageGetDataProvider.argtypes = [c_void_p]
+
+quartz.CGDataProviderCopyData.restype = c_void_p
+quartz.CGDataProviderCopyData.argtypes = [c_void_p]
+
+quartz.CGDataProviderCreateWithCFData.restype = c_void_p
+quartz.CGDataProviderCreateWithCFData.argtypes = [c_void_p]
+
+quartz.CGImageCreate.restype = c_void_p
+quartz.CGImageCreate.argtypes = [c_size_t, c_size_t, c_size_t, c_size_t,
+                                 c_size_t, c_void_p, c_uint32, c_void_p,
+                                 c_void_p, c_bool, c_int]
+
+quartz.CGImageRelease.restype = None
+quartz.CGImageRelease.argtypes = [c_void_p]
+
+quartz.CGImageGetBytesPerRow.restype = c_size_t
+quartz.CGImageGetBytesPerRow.argtypes = [c_void_p]
+
+quartz.CGImageGetWidth.restype = c_size_t
+quartz.CGImageGetWidth.argtypes = [c_void_p]
+
+quartz.CGImageGetHeight.restype = c_size_t
+quartz.CGImageGetHeight.argtypes = [c_void_p]
+
+quartz.CGImageGetBitsPerPixel.restype = c_size_t
+quartz.CGImageGetBitsPerPixel.argtypes = [c_void_p]
+
+quartz.CGImageGetBitmapInfo.restype = CGBitmapInfo
+quartz.CGImageGetBitmapInfo.argtypes = [c_void_p]
+
+quartz.CGColorSpaceCreateDeviceRGB.restype = c_void_p
+quartz.CGColorSpaceCreateDeviceRGB.argtypes = []
+
+quartz.CGDataProviderRelease.restype = None
+quartz.CGDataProviderRelease.argtypes = [c_void_p]
+
+quartz.CGColorSpaceRelease.restype = None
+quartz.CGColorSpaceRelease.argtypes = [c_void_p]
+
+quartz.CGWarpMouseCursorPosition.restype = CGError
+quartz.CGWarpMouseCursorPosition.argtypes = [CGPoint]
+
+quartz.CGDisplayMoveCursorToPoint.restype = CGError
+quartz.CGDisplayMoveCursorToPoint.argtypes = [CGDirectDisplayID, CGPoint]
+
+quartz.CGAssociateMouseAndMouseCursorPosition.restype = CGError
+quartz.CGAssociateMouseAndMouseCursorPosition.argtypes = [c_bool]
+
+quartz.CGBitmapContextCreate.restype = c_void_p
+quartz.CGBitmapContextCreate.argtypes = [
+    c_void_p, c_size_t, c_size_t, c_size_t, c_size_t, c_void_p, CGBitmapInfo]
+
+quartz.CGBitmapContextCreateImage.restype = c_void_p
+quartz.CGBitmapContextCreateImage.argtypes = [c_void_p]
+
+quartz.CGFontCreateWithDataProvider.restype = c_void_p
+quartz.CGFontCreateWithDataProvider.argtypes = [c_void_p]
+
+quartz.CGFontCreateWithFontName.restype = c_void_p
+quartz.CGFontCreateWithFontName.argtypes = [c_void_p]
+
+quartz.CGContextDrawImage.restype = None
+quartz.CGContextDrawImage.argtypes = [c_void_p, CGRect, c_void_p]
+
+quartz.CGContextRelease.restype = None
+quartz.CGContextRelease.argtypes = [c_void_p]
+
+quartz.CGContextSetTextPosition.restype = None
+quartz.CGContextSetTextPosition.argtypes = [c_void_p, CGFloat, CGFloat]
+
+quartz.CGContextSetShouldAntialias.restype = None
+quartz.CGContextSetShouldAntialias.argtypes = [c_void_p, c_bool]
+
+quartz.CGDataProviderCreateWithURL.restype = c_void_p
+quartz.CGDataProviderCreateWithURL.argtypes = [c_void_p]
+
+quartz.CGFontCreateWithDataProvider.restype = c_void_p
+quartz.CGFontCreateWithDataProvider.argtypes = [c_void_p]
+
+######################################################################
+
+# CORETEXT
+ct = cdll.LoadLibrary(util.find_library('CoreText'))
+
+# Types
+CTFontOrientation = c_uint32      # CTFontDescriptor.h
+CTFontSymbolicTraits = c_uint32   # CTFontTraits.h
+
+# CoreText constants
+kCTFontAttributeName = c_void_p.in_dll(ct, 'kCTFontAttributeName')
+kCTFontFamilyNameAttribute = c_void_p.in_dll(ct, 'kCTFontFamilyNameAttribute')
+kCTFontSymbolicTrait = c_void_p.in_dll(ct, 'kCTFontSymbolicTrait')
+kCTFontWeightTrait = c_void_p.in_dll(ct, 'kCTFontWeightTrait')
+kCTFontTraitsAttribute = c_void_p.in_dll(ct, 'kCTFontTraitsAttribute')
+
+# constants from CTFontTraits.h
+kCTFontItalicTrait = (1 << 0)
+kCTFontBoldTrait = (1 << 1)
+
+ct.CTLineCreateWithAttributedString.restype = c_void_p
+ct.CTLineCreateWithAttributedString.argtypes = [c_void_p]
+
+ct.CTLineDraw.restype = None
+ct.CTLineDraw.argtypes = [c_void_p, c_void_p]
+
+ct.CTFontGetBoundingRectsForGlyphs.restype = CGRect
+ct.CTFontGetBoundingRectsForGlyphs.argtypes = [
+    c_void_p, CTFontOrientation, POINTER(CGGlyph), POINTER(CGRect), CFIndex]
+
+ct.CTFontGetAdvancesForGlyphs.restype = c_double
+ct.CTFontGetAdvancesForGlyphs.argtypes = [
+    c_void_p, CTFontOrientation, POINTER(CGGlyph), POINTER(CGSize), CFIndex]
+
+ct.CTFontGetAscent.restype = CGFloat
+ct.CTFontGetAscent.argtypes = [c_void_p]
+
+ct.CTFontGetDescent.restype = CGFloat
+ct.CTFontGetDescent.argtypes = [c_void_p]
+
+ct.CTFontGetSymbolicTraits.restype = CTFontSymbolicTraits
+ct.CTFontGetSymbolicTraits.argtypes = [c_void_p]
+
+ct.CTFontGetGlyphsForCharacters.restype = c_bool
+ct.CTFontGetGlyphsForCharacters.argtypes = [
+    c_void_p, POINTER(UniChar), POINTER(CGGlyph), CFIndex]
+
+ct.CTFontCreateWithGraphicsFont.restype = c_void_p
+ct.CTFontCreateWithGraphicsFont.argtypes = [c_void_p, CGFloat, c_void_p,
+                                            c_void_p]
+
+ct.CTFontCopyFamilyName.restype = c_void_p
+ct.CTFontCopyFamilyName.argtypes = [c_void_p]
+
+ct.CTFontCopyFullName.restype = c_void_p
+ct.CTFontCopyFullName.argtypes = [c_void_p]
+
+ct.CTFontCreateWithFontDescriptor.restype = c_void_p
+ct.CTFontCreateWithFontDescriptor.argtypes = [c_void_p, CGFloat, c_void_p]
+
+ct.CTFontCreateCopyWithAttributes.restype = c_void_p
+ct.CTFontCreateCopyWithAttributes.argtypes = [c_void_p, CGFloat, c_void_p,
+                                              c_void_p]
+
+ct.CTFontDescriptorCreateWithAttributes.restype = c_void_p
+ct.CTFontDescriptorCreateWithAttributes.argtypes = [c_void_p]
+
+ct.CTTypesetterCreateWithAttributedString.restype = c_void_p
+ct.CTTypesetterCreateWithAttributedString.argtypes = [c_void_p]
+
+ct.CTTypesetterCreateLine.restype = c_void_p
+ct.CTTypesetterCreateLine.argtypes = [c_void_p, CFRange]
+
+ct.CTLineGetOffsetForStringIndex.restype = CGFloat
+ct.CTLineGetOffsetForStringIndex.argtypes = [c_void_p, CFIndex,
+                                             POINTER(CGFloat)]
+
+ct.CTFontManagerCreateFontDescriptorsFromURL.restype = c_void_p
+ct.CTFontManagerCreateFontDescriptorsFromURL.argtypes = [c_void_p]
+
+######################################################################
+
+# FOUNDATION
+
+# foundation = cdll.LoadLibrary(util.find_library('Foundation'))
+
+# foundation.NSMouseInRect.restype = c_bool
+# foundation.NSMouseInRect.argtypes = [NSPoint, NSRect, c_bool]
diff --git a/vispy/ext/egl.py b/vispy/ext/egl.py
new file mode 100644
index 0000000..141d7fc
--- /dev/null
+++ b/vispy/ext/egl.py
@@ -0,0 +1,369 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" A ctypes-based API to EGL.
+"""
+
+import os
+import ctypes
+from ctypes import c_int as _c_int, POINTER as _POINTER, c_void_p, c_char_p
+
+_egl_file = None
+if 'EGL_LIBRARY' in os.environ:
+    if os.path.exists(os.environ['EGL_LIBRARY']):
+        _egl_file = os.path.realpath(os.environ['EGL_LIBRARY'])
+
+# Else, try to find it
+if _egl_file is None:
+    _egl_file = ctypes.util.find_library('EGL')
+
+# Else, we failed and exit
+if _egl_file is None:
+    raise OSError('EGL library not found')
+
+# Load it
+_lib = ctypes.CDLL(_egl_file)
+
+
+## Constants
+
+EGL_FALSE = 0
+EGL_TRUE = 1
+
+# Out-of-band handle values
+EGL_DEFAULT_DISPLAY = 0
+EGL_NO_CONTEXT = 0
+EGL_NO_DISPLAY = 0
+EGL_NO_SURFACE = 0
+
+# Out-of-band attribute value
+EGL_DONT_CARE = -1
+
+# Errors / GetError return values
+EGL_SUCCESS = 0x3000
+EGL_NOT_INITIALIZED = 0x3001
+EGL_BAD_ACCESS = 0x3002
+EGL_BAD_ALLOC = 0x3003
+EGL_BAD_ATTRIBUTE = 0x3004
+EGL_BAD_CONFIG = 0x3005
+EGL_BAD_CONTEXT = 0x3006
+EGL_BAD_CURRENT_SURFACE = 0x3007
+EGL_BAD_DISPLAY = 0x3008
+EGL_BAD_MATCH = 0x3009
+EGL_BAD_NATIVE_PIXMAP = 0x300A
+EGL_BAD_NATIVE_WINDOW = 0x300B
+EGL_BAD_PARAMETER = 0x300C
+EGL_BAD_SURFACE = 0x300D
+EGL_CONTEXT_LOST = 0x300E  # EGL 1.1 - IMG_power_management
+
+# Reserved 0x300F-0x301F for additional errors
+
+# Config attributes
+EGL_BUFFER_SIZE = 0x3020
+EGL_ALPHA_SIZE = 0x3021
+EGL_BLUE_SIZE = 0x3022
+EGL_GREEN_SIZE = 0x3023
+EGL_RED_SIZE = 0x3024
+EGL_DEPTH_SIZE = 0x3025
+EGL_STENCIL_SIZE = 0x3026
+EGL_CONFIG_CAVEAT = 0x3027
+EGL_CONFIG_ID = 0x3028
+EGL_LEVEL = 0x3029
+EGL_MAX_PBUFFER_HEIGHT = 0x302A
+EGL_MAX_PBUFFER_PIXELS = 0x302B
+EGL_MAX_PBUFFER_WIDTH = 0x302C
+EGL_NATIVE_RENDERABLE = 0x302D
+EGL_NATIVE_VISUAL_ID = 0x302E
+EGL_NATIVE_VISUAL_TYPE = 0x302F
+EGL_SAMPLES = 0x3031
+EGL_SAMPLE_BUFFERS = 0x3032
+EGL_SURFACE_TYPE = 0x3033
+EGL_TRANSPARENT_TYPE = 0x3034
+EGL_TRANSPARENT_BLUE_VALUE = 0x3035
+EGL_TRANSPARENT_GREEN_VALUE = 0x3036
+EGL_TRANSPARENT_RED_VALUE = 0x3037
+EGL_NONE = 0x3038  # Attrib list terminator
+EGL_BIND_TO_TEXTURE_RGB = 0x3039
+EGL_BIND_TO_TEXTURE_RGBA = 0x303A
+EGL_MIN_SWAP_INTERVAL = 0x303B
+EGL_MAX_SWAP_INTERVAL = 0x303C
+EGL_LUMINANCE_SIZE = 0x303D
+EGL_ALPHA_MASK_SIZE = 0x303E
+EGL_COLOR_BUFFER_TYPE = 0x303F
+EGL_RENDERABLE_TYPE = 0x3040
+EGL_MATCH_NATIVE_PIXMAP = 0x3041  # Pseudo-attribute (not queryable)
+EGL_CONFORMANT = 0x3042
+
+# Reserved 0x3041-0x304F for additional config attributes
+
+# Config attribute values
+EGL_SLOW_CONFIG = 0x3050  # EGL_CONFIG_CAVEAT value
+EGL_NON_CONFORMANT_CONFIG = 0x3051  # EGL_CONFIG_CAVEAT value
+EGL_TRANSPARENT_RGB = 0x3052  # EGL_TRANSPARENT_TYPE value
+EGL_RGB_BUFFER = 0x308E  # EGL_COLOR_BUFFER_TYPE value
+EGL_LUMINANCE_BUFFER = 0x308F  # EGL_COLOR_BUFFER_TYPE value
+
+# More config attribute values, for EGL_TEXTURE_FORMAT
+EGL_NO_TEXTURE = 0x305C
+EGL_TEXTURE_RGB = 0x305D
+EGL_TEXTURE_RGBA = 0x305E
+EGL_TEXTURE_2D = 0x305F
+
+# Config attribute mask bits
+EGL_PBUFFER_BIT = 0x0001  # EGL_SURFACE_TYPE mask bits
+EGL_PIXMAP_BIT = 0x0002  # EGL_SURFACE_TYPE mask bits
+EGL_WINDOW_BIT = 0x0004  # EGL_SURFACE_TYPE mask bits
+EGL_VG_COLORSPACE_LINEAR_BIT = 0x0020  # EGL_SURFACE_TYPE mask bits
+EGL_VG_ALPHA_FORMAT_PRE_BIT = 0x0040  # EGL_SURFACE_TYPE mask bits
+EGL_MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200  # EGL_SURFACE_TYPE mask bits
+EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400  # EGL_SURFACE_TYPE mask bits
+
+EGL_OPENGL_ES_BIT = 0x0001  # EGL_RENDERABLE_TYPE mask bits
+EGL_OPENVG_BIT = 0x0002  # EGL_RENDERABLE_TYPE mask bits
+EGL_OPENGL_ES2_BIT = 0x0004  # EGL_RENDERABLE_TYPE mask bits
+EGL_OPENGL_BIT = 0x0008  # EGL_RENDERABLE_TYPE mask bits
+
+# QueryString targets
+EGL_VENDOR = 0x3053
+EGL_VERSION = 0x3054
+EGL_EXTENSIONS = 0x3055
+EGL_CLIENT_APIS = 0x308D
+
+# QuerySurface / SurfaceAttrib / CreatePbufferSurface targets
+EGL_HEIGHT = 0x3056
+EGL_WIDTH = 0x3057
+EGL_LARGEST_PBUFFER = 0x3058
+EGL_TEXTURE_FORMAT = 0x3080
+EGL_TEXTURE_TARGET = 0x3081
+EGL_MIPMAP_TEXTURE = 0x3082
+EGL_MIPMAP_LEVEL = 0x3083
+EGL_RENDER_BUFFER = 0x3086
+EGL_VG_COLORSPACE = 0x3087
+EGL_VG_ALPHA_FORMAT = 0x3088
+EGL_HORIZONTAL_RESOLUTION = 0x3090
+EGL_VERTICAL_RESOLUTION = 0x3091
+EGL_PIXEL_ASPECT_RATIO = 0x3092
+EGL_SWAP_BEHAVIOR = 0x3093
+EGL_MULTISAMPLE_RESOLVE = 0x3099
+
+# EGL_RENDER_BUFFER values / BindTexImage / ReleaseTexImage buffer targets
+EGL_BACK_BUFFER = 0x3084
+EGL_SINGLE_BUFFER = 0x3085
+
+# OpenVG color spaces
+EGL_VG_COLORSPACE_sRGB = 0x3089  # EGL_VG_COLORSPACE value
+EGL_VG_COLORSPACE_LINEAR = 0x308A  # EGL_VG_COLORSPACE value
+
+# OpenVG alpha formats
+EGL_VG_ALPHA_FORMAT_NONPRE = 0x308B  # EGL_ALPHA_FORMAT value
+EGL_VG_ALPHA_FORMAT_PRE = 0x308C  # EGL_ALPHA_FORMAT value
+
+# Constant scale factor by which fractional display resolutions &
+# * aspect ratio are scaled when queried as integer values.
+
+EGL_DISPLAY_SCALING = 10000
+
+# Unknown display resolution/aspect ratio
+EGL_UNKNOWN = -1
+
+# Back buffer swap behaviors
+EGL_BUFFER_PRESERVED = 0x3094  # EGL_SWAP_BEHAVIOR value
+EGL_BUFFER_DESTROYED = 0x3095  # EGL_SWAP_BEHAVIOR value
+
+# CreatePbufferFromClientBuffer buffer types
+EGL_OPENVG_IMAGE = 0x3096
+
+# QueryContext targets
+EGL_CONTEXT_CLIENT_TYPE = 0x3097
+
+# CreateContext attributes
+EGL_CONTEXT_CLIENT_VERSION = 0x3098
+
+# Multisample resolution behaviors
+EGL_MULTISAMPLE_RESOLVE_DEFAULT = 0x309A  # EGL_MULTISAMPLE_RESOLVE value
+EGL_MULTISAMPLE_RESOLVE_BOX = 0x309B  # EGL_MULTISAMPLE_RESOLVE value
+
+# BindAPI/QueryAPI targets
+EGL_OPENGL_ES_API = 0x30A0
+EGL_OPENVG_API = 0x30A1
+EGL_OPENGL_API = 0x30A2
+
+# GetCurrentSurface targets
+EGL_DRAW = 0x3059
+EGL_READ = 0x305A
+
+# WaitNative engines
+EGL_CORE_NATIVE_ENGINE = 0x305B
+
+# EGL 1.2 tokens renamed for consistency in EGL 1.3
+EGL_COLORSPACE = EGL_VG_COLORSPACE
+EGL_ALPHA_FORMAT = EGL_VG_ALPHA_FORMAT
+EGL_COLORSPACE_sRGB = EGL_VG_COLORSPACE_sRGB
+EGL_COLORSPACE_LINEAR = EGL_VG_COLORSPACE_LINEAR
+EGL_ALPHA_FORMAT_NONPRE = EGL_VG_ALPHA_FORMAT_NONPRE
+EGL_ALPHA_FORMAT_PRE = EGL_VG_ALPHA_FORMAT_PRE
+
+
+## The functions
+
+_lib.eglGetDisplay.argtypes = _c_int,
+_lib.eglGetDisplay.restype = c_void_p
+_lib.eglInitialize.argtypes = c_void_p, _POINTER(_c_int), _POINTER(_c_int)
+_lib.eglTerminate.argtypes = c_void_p,
+_lib.eglChooseConfig.argtypes = (c_void_p, _POINTER(_c_int),
+                                 _POINTER(c_void_p), _c_int, _POINTER(_c_int))
+_lib.eglCreateWindowSurface.argtypes = (c_void_p, c_void_p,
+                                        c_void_p, _POINTER(_c_int))
+_lib.eglCreatePbufferSurface.argtypes = (c_void_p, c_void_p, _POINTER(_c_int))
+_lib.eglCreateContext.argtypes = c_void_p, c_void_p, c_void_p, _POINTER(_c_int)
+_lib.eglMakeCurrent.argtypes = (c_void_p,) * 4
+_lib.eglSwapBuffers.argtypes = (c_void_p,) * 2
+_lib.eglDestroySurface.argtypes = (c_void_p,) * 2
+_lib.eglQueryString.argtypes = (c_void_p, _c_int)
+_lib.eglQueryString.restype = c_char_p
+
+
+def eglGetError():
+    """ Check for errors, returns an enum (int).
+    """
+    return _lib.eglGetError()
+
+
+def eglGetDisplay(display=EGL_DEFAULT_DISPLAY):
+    """ Connect to the EGL display server.
+    """
+    res = _lib.eglGetDisplay(display)
+    if not res or res == EGL_NO_DISPLAY:
+        raise RuntimeError('Could not create display')
+    return res
+
+
+def eglInitialize(display):
+    """ Initialize EGL and return EGL version tuple.
+    """
+    majorVersion = (_c_int*1)()
+    minorVersion = (_c_int*1)()
+    res = _lib.eglInitialize(display, majorVersion, minorVersion)
+    if res == EGL_FALSE:
+        raise RuntimeError('Could not initialize')
+    return majorVersion[0], minorVersion[0]
+
+
+def eglTerminate(display):
+    """ Initialize EGL and return EGL version tuple.
+    """
+    _lib.eglTerminate(display)
+
+
+def eglQueryString(display, name):
+    """ Query string from display
+    """
+    out = _lib.eglQueryString(display, name)
+    if not out:
+        raise RuntimeError('Could not query %s' % name)
+    return out
+
+
+DEFAULT_ATTRIB_LIST = (EGL_RED_SIZE, 8, EGL_BLUE_SIZE, 8,
+                       EGL_GREEN_SIZE, 8, EGL_ALPHA_SIZE, 8,
+                       EGL_BIND_TO_TEXTURE_RGBA, EGL_TRUE,
+                       EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
+                       EGL_CONFORMANT, EGL_OPENGL_ES2_BIT,
+                       EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                       EGL_NATIVE_RENDERABLE, EGL_TRUE,
+                       EGL_SURFACE_TYPE, EGL_PBUFFER_BIT)
+
+
+def _convert_attrib_list(attribList):
+    attribList = attribList or []
+    attribList = [a for a in attribList] + [EGL_NONE]
+    attribList = (_c_int*len(attribList))(*attribList)
+    return attribList
+
+
+def eglChooseConfig(display, attribList=DEFAULT_ATTRIB_LIST):
+    attribList = _convert_attrib_list(attribList)
+    numConfigs = (_c_int*1)()
+    _lib.eglChooseConfig(display, attribList, None, 0, numConfigs)
+    n = numConfigs[0]
+    if n <= 0:
+        raise RuntimeError('Could not find any suitable config.')
+    config = (c_void_p*n)()
+    _lib.eglChooseConfig(display, attribList, config, n, numConfigs)
+    return config
+
+
+def _check_res(res):
+    if res == EGL_NO_SURFACE:
+        e = eglGetError()
+    else:
+        return res
+    if e == EGL_BAD_MATCH:
+        raise ValueError('Cannot create surface: attributes do not match ' +
+                         'or given config cannot render in window.')
+    elif e == EGL_BAD_CONFIG:
+        raise ValueError('Cannot create surface: given config is not ' +
+                         'supported by this system.')
+    elif e == EGL_BAD_NATIVE_WINDOW:
+        raise ValueError('Cannot create surface: the given native window ' +
+                         'handle is invalid.')
+    elif e == EGL_BAD_ALLOC:
+        raise RuntimeError('Could not allocate surface: not enough ' +
+                           'resources or native window already associated ' +
+                           'with another config.')
+    else:
+        raise RuntimeError('Could not create window surface due to ' +
+                           'unknown error: %i' % e)
+
+
+def eglCreateWindowSurface(display, config, window, attribList=None):
+    # Deal with attrib list
+    attribList = _convert_attrib_list(attribList)
+    return _check_res(_lib.eglCreateWindowSurface(display, config,
+                                                  window, attribList))
+
+
+def eglCreatePbufferSurface(display, config, attribList=None):
+    # Deal with attrib list
+    attribList = _convert_attrib_list(attribList)
+    #
+    return _check_res(_lib.eglCreatePbufferSurface(display, config,
+                                                   attribList))
+
+
+def eglCreateContext(display, config, shareContext=EGL_NO_CONTEXT,
+                     attribList=None):
+    # Deal with attrib list
+    attribList = attribList or [EGL_CONTEXT_CLIENT_VERSION, 2]
+    attribList = [a for a in attribList] + [EGL_NONE]
+    attribList = (_c_int*len(attribList))(*attribList)
+    #
+    res = _lib.eglCreateContext(display, config, shareContext, attribList)
+    if res == EGL_NO_CONTEXT:
+        e = eglGetError()
+        if e == EGL_BAD_CONFIG:
+            raise ValueError('Could not create context: given config is ' +
+                             'not supported by this system.')
+        else:
+            raise RuntimeError('Could not create context due to ' +
+                               'unknown error: %i' % e)
+    return res
+
+
+def eglMakeCurrent(display, draw, read, context):
+    res = _lib.eglMakeCurrent(display, draw, read, context)
+    if res == EGL_FALSE:
+        raise RuntimeError('Could not make the context current.')
+
+
+def eglSwapBuffers(display, surface):
+    res = _lib.eglSwapBuffers(display, surface)
+    if res == EGL_FALSE:
+        raise RuntimeError('Could not swap buffers.')
+
+
+def eglDestroySurface(display, surface):
+    res = _lib.eglDestroySurface(display, surface)
+    if res == EGL_FALSE:
+        raise RuntimeError('Could not destroy surface')
diff --git a/vispy/ext/fontconfig.py b/vispy/ext/fontconfig.py
new file mode 100644
index 0000000..6033b49
--- /dev/null
+++ b/vispy/ext/fontconfig.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+
+import warnings
+from ctypes import (util, cdll, c_void_p, c_char_p, c_double, c_int, c_bool,
+                    Union, Structure, byref, POINTER)
+from ..util.wrappers import run_subprocess
+
+# Some code adapted from Pyglet
+
+fc = util.find_library('fontconfig')
+if fc is None:
+    raise ImportError('fontconfig not found')
+fontconfig = cdll.LoadLibrary(fc)
+
+FC_FAMILY = 'family'.encode('ASCII')
+FC_SIZE = 'size'.encode('ASCII')
+FC_SLANT = 'slant'.encode('ASCII')
+FC_WEIGHT = 'weight'.encode('ASCII')
+FC_FT_FACE = 'ftface'.encode('ASCII')
+FC_FILE = 'file'.encode('ASCII')
+FC_STYLE = 'style'.encode('ASCII')
+FC_LANG = 'lang'.encode('ASCII')
+FC_WEIGHT_REGULAR = 80
+FC_WEIGHT_BOLD = 200
+FC_SLANT_ROMAN = 0
+FC_SLANT_ITALIC = 100
+
+FcMatchPattern = 1
+FcTypeVoid = 0
+FcTypeInteger = 1
+FcTypeDouble = 2
+FcTypeString = 3
+FcTypeBool = 4
+FcTypeMatrix = 5
+FcTypeCharSet = 6
+FcTypeFTFace = 7
+FcTypeLangSet = 8
+FcType = c_int
+
+
+class _FcValueUnion(Union):
+    _fields_ = [('s', c_char_p), ('i', c_int), ('b', c_int), ('d', c_double),
+                ('m', c_void_p), ('c', c_void_p), ('f', c_void_p),
+                ('p', c_void_p), ('l', c_void_p)]
+
+
+class FcValue(Structure):
+    _fields_ = [('type', FcType), ('u', _FcValueUnion)]
+
+
+class FcFontSet(Structure):
+    _fields_ = [('nfont', c_int), ('sfont', c_int),
+                ('fonts', POINTER(c_void_p))]
+
+
+class FcObjectSet(Structure):
+    _fields_ = [('nobject', c_int), ('sobject', c_int), ('objects', c_void_p)]
+
+fontconfig.FcConfigSubstitute.argtypes = [c_void_p, c_void_p, c_int]
+fontconfig.FcDefaultSubstitute.argtypes = [c_void_p]
+fontconfig.FcFontMatch.restype = c_void_p
+fontconfig.FcFontMatch.argtypes = [c_void_p, c_void_p, c_void_p]
+fontconfig.FcPatternBuild.restype = c_void_p
+fontconfig.FcPatternCreate.restype = c_void_p
+fontconfig.FcPatternAddDouble.argtypes = [c_void_p, c_char_p, c_double]
+fontconfig.FcPatternAddInteger.argtypes = [c_void_p, c_char_p, c_int]
+fontconfig.FcPatternAddString.argtypes = [c_void_p, c_char_p, c_char_p]
+fontconfig.FcPatternDestroy.argtypes = [c_void_p]
+fontconfig.FcPatternGetFTFace.argtypes = [c_void_p, c_char_p, c_int, c_void_p]
+fontconfig.FcPatternGet.argtypes = [c_void_p, c_char_p, c_int, c_void_p]
+
+fontconfig.FcObjectSetBuild.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p]
+fontconfig.FcObjectSetBuild.restype = FcObjectSet
+fontconfig.FcFontList.restype = FcFontSet
+fontconfig.FcFontList.argtypes = [c_void_p, c_void_p, FcObjectSet]
+fontconfig.FcNameUnparse.restype = c_char_p
+fontconfig.FcNameUnparse.argtypes = [c_void_p]
+fontconfig.FcFontSetDestroy.argtypes = [FcFontSet]
+fontconfig.FcFontSort.restype = FcFontSet
+fontconfig.FcFontSort.argtypes = [c_void_p, c_void_p, c_bool,
+                                  c_void_p, c_void_p]
+fontconfig.FcConfigGetCurrent.restype = c_void_p
+
+
+def find_font(face, bold, italic):
+    """Find font"""
+    bold = FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR
+    italic = FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN
+    face = face.encode('utf8')
+    fontconfig.FcInit()
+    pattern = fontconfig.FcPatternCreate()
+    fontconfig.FcPatternAddInteger(pattern, FC_WEIGHT, bold)
+    fontconfig.FcPatternAddInteger(pattern, FC_SLANT, italic)
+    fontconfig.FcPatternAddString(pattern, FC_FAMILY, face)
+    fontconfig.FcConfigSubstitute(0, pattern, FcMatchPattern)
+    fontconfig.FcDefaultSubstitute(pattern)
+    result = FcType()
+    match = fontconfig.FcFontMatch(0, pattern, byref(result))
+    fontconfig.FcPatternDestroy(pattern)
+    if not match:
+        raise RuntimeError('Could not match font "%s"' % face)
+    value = FcValue()
+    fontconfig.FcPatternGet(match, FC_FAMILY, 0, byref(value))
+    if(value.u.s != face):
+        warnings.warn('Could not find face match "%s", falling back to "%s"'
+                      % (face, value.u.s))
+    result = fontconfig.FcPatternGet(match, FC_FILE, 0, byref(value))
+    if result != 0:
+        raise RuntimeError('No filename or FT face for "%s"' % face)
+    fname = value.u.s
+    return fname.decode('utf-8')
+
+
+def _list_fonts():
+    """List system fonts"""
+    stdout_, stderr = run_subprocess(['fc-list', ':', 'family'])
+    vals = stdout_.decode('utf-8').strip().split('\n')
+    vals = [v.split(',')[0] for v in vals]
+    return vals
diff --git a/vispy/ext/freetype.py b/vispy/ext/freetype.py
new file mode 100644
index 0000000..e766555
--- /dev/null
+++ b/vispy/ext/freetype.py
@@ -0,0 +1,499 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+#
+#  FreeType high-level python API - Copyright 2011 Nicolas P. Rougier
+#  Distributed under the terms of the new BSD license.
+#
+# -----------------------------------------------------------------------------
+'''
+FreeType high-level python API
+
+Adapted from freetype-py.
+'''
+
+import sys
+import struct
+from ctypes import (byref, c_char_p, c_ushort, cast, util, CDLL, Structure,
+                    POINTER, c_int, c_short, c_long, c_void_p, c_uint,
+                    c_char, c_ubyte, CFUNCTYPE)
+
+from .six import string_types
+from ..util.fetching import load_data_file
+
+FT_LOAD_RENDER = 4
+FT_KERNING_DEFAULT = 0
+FT_KERNING_UNFITTED = 1
+FT_LOAD_NO_HINTING = 2
+FT_LOAD_FORCE_AUTOHINT = 32
+FT_LOAD_NO_AUTOHINT = 32768
+FT_LOAD_TARGET_LCD = 196608
+FT_LOAD_TARGET_LIGHT = 65536
+FT_LOAD_NO_SCALE = 1
+
+_64_bit = (8 * struct.calcsize("P")) == 64
+##############################################################################
+# ft_structs
+
+FT_Int = c_int
+FT_UInt = c_uint
+FT_F2Dot14 = c_short
+FT_Pos = FT_Fixed = FT_Long = c_long
+FT_Glyph_Format = c_int
+FT_String_p = c_char_p
+FT_Short = c_short  # A typedef for signed short.
+FT_UShort = c_ushort  # A typedef for unsigned short.
+FT_Generic_Finalizer = CFUNCTYPE(None, c_void_p)
+FT_Encoding = c_int
+
+
+class FT_LibraryRec(Structure):
+    _fields_ = []
+FT_Library = POINTER(FT_LibraryRec)
+
+
+class FT_Vector(Structure):
+    _fields_ = [('x', FT_Pos), ('y', FT_Pos)]
+
+
+class FT_UnitVector(Structure):
+    _fields_ = [('x', FT_F2Dot14), ('y', FT_F2Dot14)]
+
+
+class FT_Matrix(Structure):
+    _fields_ = [('xx', FT_Fixed), ('xy', FT_Fixed),
+                ('yx', FT_Fixed), ('yy', FT_Fixed)]
+
+
+class FT_GlyphRec(Structure):
+    _fields_ = [('library', FT_Library), ('clazz', c_void_p),
+                ('format', FT_Glyph_Format), ('advance', FT_Vector)]
+FT_Glyph = POINTER(FT_GlyphRec)
+
+
+class FT_Bitmap(Structure):
+    _fields_ = [('rows', c_int), ('width', c_int),
+                ('pitch', c_int), ('buffer', POINTER(c_ubyte)),
+                ('num_grays', c_short), ('pixel_mode', c_ubyte),
+                ('palette_mode', c_char), ('palette', c_void_p)]
+
+
+class FT_BitmapGlyphRec(Structure):
+    _fields_ = [('root', FT_GlyphRec), ('left', FT_Int),
+                ('top', FT_Int), ('bitmap', FT_Bitmap)]
+FT_BitmapGlyph = POINTER(FT_BitmapGlyphRec)
+
+
+class FT_Glyph_Metrics(Structure):
+    _fields_ = [('width', FT_Pos), ('height', FT_Pos),
+                ('horiBearingX', FT_Pos), ('horiBearingY', FT_Pos),
+                ('horiAdvance',  FT_Pos), ('vertBearingX', FT_Pos),
+                ('vertBearingY', FT_Pos), ('vertAdvance',  FT_Pos)]
+
+
+class FT_Outline(Structure):
+    _fields_ = [('n_contours', c_short), ('n_points', c_short),
+                ('points', POINTER(FT_Vector)), ('tags', POINTER(c_ubyte)),
+                ('contours', POINTER(c_short)), ('flags', c_int)]
+
+
+class FT_Size_Metrics(Structure):
+    _fields_ = [('x_ppem', FT_UShort), ('y_ppem', FT_UShort),
+                ('x_scale', FT_Fixed), ('y_scale', FT_Fixed),
+                ('ascender', FT_Pos),  ('descender', FT_Pos),
+                ('height', FT_Pos), ('max_advance', FT_Pos)]
+
+
+class FT_BBox(Structure):
+    _fields_ = [('xMin', FT_Pos), ('yMin', FT_Pos),
+                ('xMax', FT_Pos), ('yMax', FT_Pos)]
+
+
+class FT_Generic(Structure):
+    _fields_ = [('data', c_void_p), ('finalizer', FT_Generic_Finalizer)]
+
+
+class FT_SizeRec(Structure):
+    _fields_ = [('face', c_void_p), ('generic', FT_Generic),
+                ('metrics', FT_Size_Metrics), ('internal', c_void_p)]
+FT_Size = POINTER(FT_SizeRec)
+
+
+class FT_CharmapRec(Structure):
+    _fields_ = [('face', c_void_p), ('encoding', FT_Encoding),
+                ('platform_id', FT_UShort), ('encoding_id', FT_UShort)]
+FT_Charmap = POINTER(FT_CharmapRec)
+
+
+class FT_Bitmap_Size(Structure):
+    _fields_ = [('height', FT_Short), ('width', FT_Short),
+                ('size', FT_Pos), ('x_ppem', FT_Pos), ('y_ppem', FT_Pos)]
+
+
+class FT_GlyphSlotRec(Structure):
+    _fields_ = [('library', FT_Library), ('face', c_void_p),
+                ('next', c_void_p), ('reserved', c_uint),
+                ('generic', FT_Generic), ('metrics', FT_Glyph_Metrics),
+                ('linearHoriAdvance', FT_Fixed),
+                ('linearVertAdvance', FT_Fixed),
+                ('advance', FT_Vector), ('format',  FT_Glyph_Format),
+                ('bitmap', FT_Bitmap), ('bitmap_left', FT_Int),
+                ('bitmap_top', FT_Int), ('outline', FT_Outline),
+                ('num_subglyphs', FT_UInt), ('subglyphs', c_void_p),
+                ('control_data', c_void_p), ('control_len', c_long),
+                ('lsb_delta', FT_Pos), ('rsb_delta', FT_Pos),
+                ('other', c_void_p), ('internal', c_void_p)]
+FT_GlyphSlot = POINTER(FT_GlyphSlotRec)
+
+
+class FT_FaceRec(Structure):
+    _fields_ = [('num_faces', FT_Long), ('face_index', FT_Long),
+                ('face_flags', FT_Long), ('style_flags', FT_Long),
+                ('num_glyphs', FT_Long), ('family_name', FT_String_p),
+                ('style_name', FT_String_p), ('num_fixed_sizes', FT_Int),
+                ('available_sizes', POINTER(FT_Bitmap_Size)),
+                ('num_charmaps', c_int), ('charmaps', POINTER(FT_Charmap)),
+                ('generic', FT_Generic), ('bbox', FT_BBox),
+                ('units_per_EM', FT_UShort), ('ascender', FT_Short),
+                ('descender', FT_Short), ('height', FT_Short),
+                ('max_advance_width', FT_Short),
+                ('max_advance_height', FT_Short),
+                ('underline_position',  FT_Short),
+                ('underline_thickness', FT_Short),
+                ('glyph', FT_GlyphSlot), ('size', FT_Size),
+                ('charmap', FT_Charmap),
+                ('driver', c_void_p), ('memory', c_void_p),
+                ('stream', c_void_p), ('sizes_list_head', c_void_p),
+                ('sizes_list_tail', c_void_p), ('autohint', FT_Generic),
+                ('extensions', c_void_p), ('internal', c_void_p)]
+FT_Face = POINTER(FT_FaceRec)
+
+
+##############################################################################
+# __init__.py
+
+__dll__ = None
+FT_Library_filename = util.find_library('freetype')
+if not FT_Library_filename and sys.platform.startswith('win'):
+    fname_end = '_x64.dll' if _64_bit else '.dll'
+    FT_Library_filename = load_data_file('freetype/freetype253' + fname_end)
+if not FT_Library_filename:
+    raise ImportError('Freetype library not found')
+if not __dll__:
+    __dll__ = CDLL(FT_Library_filename)
+
+
+FT_Init_FreeType = __dll__.FT_Init_FreeType
+FT_Done_FreeType = __dll__.FT_Done_FreeType
+FT_Library_Version = __dll__.FT_Library_Version
+__handle__ = None
+
+
+# Comment out to avoid segfaults on Py34
+# def __del_library__(self):
+#     global __handle__
+#     if __handle__:
+#         try:
+#             FT_Done_FreeType(self)
+#             __handle__ = None
+#         except:
+#             pass
+# FT_Library.__del__ = __del_library__
+
+
+def get_handle():
+    '''
+    Get unique FT_Library handle
+    '''
+    global __handle__
+    if not __handle__:
+        __handle__ = FT_Library()
+        error = FT_Init_FreeType(byref(__handle__))
+        if error:
+            raise RuntimeError(error)
+    return __handle__
+
+
+def version():
+    '''
+    Return the version of the FreeType library being used as a tuple of
+    ( major version number, minor version number, patch version number )
+    '''
+    amajor = FT_Int()
+    aminor = FT_Int()
+    apatch = FT_Int()
+    library = get_handle()
+    FT_Library_Version(library, byref(amajor), byref(aminor), byref(apatch))
+    return (amajor.value, aminor.value, apatch.value)
+
+
+try:
+    FT_Library_SetLcdFilter = __dll__.FT_Library_SetLcdFilter
+except:
+    def FT_Library_SetLcdFilter(*args, **kwargs):
+        return 0
+if version() >= (2, 4, 0):
+    FT_Library_SetLcdFilterWeights = __dll__.FT_Library_SetLcdFilterWeights
+FT_New_Face = __dll__.FT_New_Face
+FT_New_Memory_Face = __dll__.FT_New_Memory_Face
+FT_Open_Face = __dll__.FT_Open_Face
+FT_Attach_File = __dll__.FT_Attach_File
+FT_Attach_Stream = __dll__.FT_Attach_Stream
+if version() >= (2, 4, 2):
+    FT_Reference_Face = __dll__.FT_Reference_Face
+FT_Done_Face = __dll__.FT_Done_Face
+FT_Done_Glyph = __dll__.FT_Done_Glyph
+FT_Select_Size = __dll__.FT_Select_Size
+FT_Request_Size = __dll__.FT_Request_Size
+FT_Set_Char_Size = __dll__.FT_Set_Char_Size
+FT_Set_Pixel_Sizes = __dll__.FT_Set_Pixel_Sizes
+FT_Load_Glyph = __dll__.FT_Load_Glyph
+FT_Load_Char = __dll__.FT_Load_Char
+FT_Set_Transform = __dll__.FT_Set_Transform
+FT_Render_Glyph = __dll__.FT_Render_Glyph
+FT_Get_Kerning = __dll__.FT_Get_Kerning
+FT_Get_Track_Kerning = __dll__.FT_Get_Track_Kerning
+FT_Get_Glyph_Name = __dll__.FT_Get_Glyph_Name
+FT_Get_Glyph = __dll__.FT_Get_Glyph
+
+FT_Glyph_Get_CBox = __dll__.FT_Glyph_Get_CBox
+
+FT_Get_Postscript_Name = __dll__.FT_Get_Postscript_Name
+FT_Get_Postscript_Name.restype = c_char_p
+FT_Select_Charmap = __dll__.FT_Select_Charmap
+FT_Set_Charmap = __dll__.FT_Set_Charmap
+FT_Get_Charmap_Index = __dll__.FT_Get_Charmap_Index
+FT_Get_CMap_Language_ID = __dll__.FT_Get_CMap_Language_ID
+FT_Get_CMap_Format = __dll__.FT_Get_CMap_Format
+FT_Get_Char_Index = __dll__.FT_Get_Char_Index
+FT_Get_First_Char = __dll__.FT_Get_First_Char
+FT_Get_Next_Char = __dll__.FT_Get_Next_Char
+FT_Get_Name_Index = __dll__.FT_Get_Name_Index
+FT_Get_SubGlyph_Info = __dll__.FT_Get_SubGlyph_Info
+if version() >= (2, 3, 8):
+    FT_Get_FSType_Flags = __dll__.FT_Get_FSType_Flags
+    FT_Get_FSType_Flags.restype = c_ushort
+
+FT_Get_X11_Font_Format = __dll__.FT_Get_X11_Font_Format
+FT_Get_X11_Font_Format.restype = c_char_p
+
+FT_Get_Sfnt_Name_Count = __dll__.FT_Get_Sfnt_Name_Count
+FT_Get_Sfnt_Name = __dll__.FT_Get_Sfnt_Name
+FT_Get_Advance = __dll__.FT_Get_Advance
+
+
+FT_Outline_GetInsideBorder = __dll__.FT_Outline_GetInsideBorder
+FT_Outline_GetOutsideBorder = __dll__.FT_Outline_GetOutsideBorder
+FT_Outline_Get_BBox = __dll__.FT_Outline_Get_BBox
+FT_Outline_Get_CBox = __dll__.FT_Outline_Get_CBox
+FT_Stroker_New = __dll__.FT_Stroker_New
+FT_Stroker_Set = __dll__.FT_Stroker_Set
+FT_Stroker_Rewind = __dll__.FT_Stroker_Rewind
+FT_Stroker_ParseOutline = __dll__.FT_Stroker_ParseOutline
+FT_Stroker_BeginSubPath = __dll__.FT_Stroker_BeginSubPath
+FT_Stroker_EndSubPath = __dll__.FT_Stroker_EndSubPath
+FT_Stroker_LineTo = __dll__.FT_Stroker_LineTo
+FT_Stroker_ConicTo = __dll__.FT_Stroker_ConicTo
+FT_Stroker_CubicTo = __dll__.FT_Stroker_CubicTo
+FT_Stroker_GetBorderCounts = __dll__.FT_Stroker_GetBorderCounts
+FT_Stroker_ExportBorder = __dll__.FT_Stroker_ExportBorder
+FT_Stroker_GetCounts = __dll__.FT_Stroker_GetCounts
+FT_Stroker_Export = __dll__.FT_Stroker_Export
+FT_Stroker_Done = __dll__.FT_Stroker_Done
+FT_Glyph_Stroke = __dll__.FT_Glyph_Stroke
+FT_Glyph_StrokeBorder = __dll__.FT_Glyph_StrokeBorder
+FT_Glyph_To_Bitmap = __dll__.FT_Glyph_To_Bitmap
+
+
+Vector = FT_Vector
+Matrix = FT_Matrix
+
+
+class Bitmap(object):
+    def __init__(self, bitmap):
+        self._FT_Bitmap = bitmap
+
+    rows = property(lambda self: self._FT_Bitmap.rows)
+    width = property(lambda self: self._FT_Bitmap.width)
+    pitch = property(lambda self: self._FT_Bitmap.pitch)
+    buffer = property(lambda self:
+                      [self._FT_Bitmap.buffer[i]
+                       for i in range(self.rows * self.pitch)])
+
+
+class Glyph(object):
+    def __init__(self, glyph):
+        self._FT_Glyph = glyph
+
+    def __del__(self):
+        if self._FT_Glyph is not None and FT_Done_Glyph is not None:
+            FT_Done_Glyph(self._FT_Glyph)
+
+    def to_bitmap(self, mode, origin, destroy=False):
+        error = FT_Glyph_To_Bitmap(byref(self._FT_Glyph),
+                                   mode, origin, destroy)
+        if error:
+            raise RuntimeError(error)
+        return BitmapGlyph(self._FT_Glyph)
+
+
+class BitmapGlyph(object):
+    def __init__(self, glyph):
+        self._FT_BitmapGlyph = cast(glyph, FT_BitmapGlyph)
+
+    bitmap = property(lambda self:
+                      Bitmap(self._FT_BitmapGlyph.contents.bitmap))
+    left = property(lambda self: self._FT_BitmapGlyph.contents.left)
+    top = property(lambda self: self._FT_BitmapGlyph.contents.top)
+
+
+class GlyphSlot(object):
+    def __init__(self, slot):
+        self._FT_GlyphSlot = slot
+
+    def get_glyph(self):
+        aglyph = FT_Glyph()
+        error = FT_Get_Glyph(self._FT_GlyphSlot, byref(aglyph))
+        if error:
+            raise RuntimeError(error)
+        return Glyph(aglyph)
+
+    bitmap = property(lambda self: Bitmap(self._FT_GlyphSlot.contents.bitmap))
+    metrics = property(lambda self: self._FT_GlyphSlot.contents.metrics)
+    next = property(lambda self: GlyphSlot(self._FT_GlyphSlot.contents.next))
+    advance = property(lambda self: self._FT_GlyphSlot.contents.advance)
+    format = property(lambda self: self._FT_GlyphSlot.contents.format)
+    bitmap_top = property(lambda self: self._FT_GlyphSlot.contents.bitmap_top)
+    bitmap_left = property(lambda self:
+                           self._FT_GlyphSlot.contents.bitmap_left)
+
+
+class Face(object):
+    def __init__(self, filename, index=0):
+        library = get_handle()
+        face = FT_Face()
+        self._FT_Face = None
+        # error = FT_New_Face( library, filename, 0, byref(face) )
+        u_filename = c_char_p(filename.encode('utf-8'))
+        error = FT_New_Face(library, u_filename, index, byref(face))
+        if error:
+            raise RuntimeError(error)
+        self._filename = filename
+        self._index = index
+        self._FT_Face = face
+
+    def __del__(self):
+        if self._FT_Face is not None and FT_Done_Face is not None:
+            FT_Done_Face(self._FT_Face)
+
+    def attach_file(self, filename):
+        error = FT_Attach_File(self._FT_Face, filename)
+        if error:
+            raise RuntimeError(error)
+
+    def set_char_size(self, width=0, height=0, hres=72, vres=72):
+        error = FT_Set_Char_Size(self._FT_Face, width, height, hres, vres)
+        if error:
+            raise RuntimeError(error)
+
+    def set_pixel_sizes(self, width, height):
+        error = FT_Set_Pixel_Sizes(self._FT_Face, width, height)
+        if error:
+            raise RuntimeError(error)
+
+    def select_charmap(self, encoding):
+        error = FT_Select_Charmap(self._FT_Face, encoding)
+        if error:
+            raise RuntimeError(error)
+
+    def set_charmap(self, charmap):
+        error = FT_Set_Charmap(self._FT_Face, charmap._FT_Charmap)
+        if error:
+            raise RuntimeError(error)
+
+    def get_char_index(self, charcode):
+        if isinstance(charcode, string_types):
+            charcode = ord(charcode)
+        return FT_Get_Char_Index(self._FT_Face, charcode)
+
+    def get_first_char(self):
+        agindex = FT_UInt()
+        charcode = FT_Get_First_Char(self._FT_Face, byref(agindex))
+        return charcode, agindex.value
+
+    def get_next_char(self, charcode, agindex):
+        agindex = FT_UInt(0)  # agindex )
+        charcode = FT_Get_Next_Char(self._FT_Face, charcode, byref(agindex))
+        return charcode, agindex.value
+
+    def get_name_index(self, name):
+        return FT_Get_Name_Index(self._FT_Face, name)
+
+    def set_transform(self, matrix, delta):
+        FT_Set_Transform(self._FT_Face,
+                         byref(matrix), byref(delta))
+
+    def select_size(self, strike_index):
+        error = FT_Select_Size(self._FT_Face, strike_index)
+        if error:
+            raise RuntimeError(error)
+
+    def load_glyph(self, index, flags=FT_LOAD_RENDER):
+        error = FT_Load_Glyph(self._FT_Face, index, flags)
+        if error:
+            raise RuntimeError(error)
+
+    def load_char(self, char, flags=FT_LOAD_RENDER):
+        if len(char) == 1:
+            char = ord(char)
+        error = FT_Load_Char(self._FT_Face, char, flags)
+        if error:
+            raise RuntimeError(error)
+
+    def get_advance(self, gindex, flags):
+        padvance = FT_Fixed(0)
+        error = FT_Get_Advance(self._FT_Face, gindex, flags, byref(padvance))
+        if error:
+            raise RuntimeError(error)
+        return padvance.value
+
+    def get_kerning(self, left, right, mode=FT_KERNING_DEFAULT):
+        left_glyph = self.get_char_index(left)
+        right_glyph = self.get_char_index(right)
+        kerning = FT_Vector(0, 0)
+        error = FT_Get_Kerning(self._FT_Face,
+                               left_glyph, right_glyph, mode, byref(kerning))
+        if error:
+            raise RuntimeError(error)
+        return kerning
+
+    def get_format(self):
+        return FT_Get_X11_Font_Format(self._FT_Face)
+
+    sfnt_name_count = property(lambda self:
+                               FT_Get_Sfnt_Name_Count(self._FT_Face))
+    postscript_name = property(lambda self:
+                               FT_Get_Postscript_Name(self._FT_Face))
+    num_faces = property(lambda self: self._FT_Face.contents.num_faces)
+    face_index = property(lambda self: self._FT_Face.contents.face_index)
+    face_flags = property(lambda self: self._FT_Face.contents.face_flags)
+    style_flags = property(lambda self: self._FT_Face.contents.style_flags)
+    num_glyphs = property(lambda self: self._FT_Face.contents.num_glyphs)
+    family_name = property(lambda self: self._FT_Face.contents.family_name)
+    style_name = property(lambda self: self._FT_Face.contents.style_name)
+    num_fixed_sizes = property(lambda self:
+                               self._FT_Face.contents.num_fixed_sizes)
+    num_charmaps = property(lambda self: self._FT_Face.contents.num_charmaps)
+    units_per_EM = property(lambda self: self._FT_Face.contents.units_per_EM)
+    ascender = property(lambda self: self._FT_Face.contents.ascender)
+    descender = property(lambda self: self._FT_Face.contents.descender)
+    height = property(lambda self: self._FT_Face.contents.height)
+    max_advance_width = property(lambda self:
+                                 self._FT_Face.contents.max_advance_width)
+    max_advance_height = property(lambda self:
+                                  self._FT_Face.contents.max_advance_height)
+    underline_position = property(lambda self:
+                                  self._FT_Face.contents.underline_position)
+    underline_thickness = property(lambda self:
+                                   self._FT_Face.contents.underline_thickness)
+    glyph = property(lambda self: GlyphSlot(self._FT_Face.contents.glyph))
diff --git a/vispy/ext/gdi32plus.py b/vispy/ext/gdi32plus.py
new file mode 100644
index 0000000..71433db
--- /dev/null
+++ b/vispy/ext/gdi32plus.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+# Adapted from Pyglet
+
+import atexit
+from functools import partial
+import struct
+
+from ctypes import (windll, Structure, POINTER, byref, WINFUNCTYPE,
+                    c_uint, c_float, c_int, c_ulong, c_uint64,
+                    c_void_p, c_uint32, c_wchar, c_wchar_p)
+from ctypes.wintypes import (LONG, BYTE, HFONT, HGDIOBJ, BOOL, UINT, INT,
+                             DWORD, LPARAM)
+
+try:
+    import _winreg as winreg
+except ImportError:
+    import winreg  # noqa, analysis:ignore
+
+_64_bit = (8 * struct.calcsize("P")) == 64
+
+LF_FACESIZE = 32
+FW_BOLD = 700
+FW_NORMAL = 400
+ANTIALIASED_QUALITY = 4
+FontStyleBold = 1
+FontStyleItalic = 2
+UnitPixel = 2
+UnitPoint = 3
+DEFAULT_CHARSET = 1
+ANSI_CHARSET = 0
+TRUETYPE_FONTTYPE = 4
+GM_ADVANCED = 2
+CSIDL_FONTS = 0x0014
+
+PixelFormat24bppRGB = 137224
+PixelFormat32bppRGB = 139273
+PixelFormat32bppARGB = 2498570
+
+DriverStringOptionsCmapLookup = 1
+DriverStringOptionsRealizedAdvance = 4
+TextRenderingHintAntiAlias = 4
+TextRenderingHintAntiAliasGridFit = 3
+ImageLockModeRead = 1
+StringFormatFlagsMeasureTrailingSpaces = 0x00000800
+StringFormatFlagsNoClip = 0x00004000
+StringFormatFlagsNoFitBlackBox = 0x00000004
+
+INT_PTR = c_int
+REAL = c_float
+TCHAR = c_wchar
+UINT32 = c_uint32
+HDC = c_void_p
+PSTR = c_uint64 if _64_bit else c_uint
+
+
+# gdi32
+
+class POINT(Structure):
+    _fields_ = [('x', LONG), ('y', LONG)]
+
+
+class RECT(Structure):
+    _fields_ = [('left', LONG), ('top', LONG),
+                ('right', LONG), ('bottom', LONG)]
+
+
+class PANOSE(Structure):
+    _fields_ = [
+        ('bFamilyType', BYTE), ('bSerifStyle', BYTE), ('bWeight', BYTE),
+        ('bProportion', BYTE), ('bContrast', BYTE), ('bStrokeVariation', BYTE),
+        ('bArmStyle', BYTE), ('bLetterform', BYTE), ('bMidline', BYTE),
+        ('bXHeight', BYTE)]
+
+
+class TEXTMETRIC(Structure):
+    _fields_ = [
+        ('tmHeight', LONG), ('tmAscent', LONG), ('tmDescent', LONG),
+        ('tmInternalLeading', LONG), ('tmExternalLeading', LONG),
+        ('tmAveCharWidth', LONG), ('tmMaxCharWidth', LONG),
+        ('tmWeight', LONG), ('tmOverhang', LONG),
+        ('tmDigitizedAspectX', LONG), ('tmDigitizedAspectY', LONG),
+        ('tmFirstChar', TCHAR), ('tmLastChar', TCHAR),
+        ('tmDefaultChar', TCHAR), ('tmBreakChar', TCHAR),
+        ('tmItalic', BYTE), ('tmUnderlined', BYTE),
+        ('tmStruckOut', BYTE), ('tmPitchAndFamily', BYTE),
+        ('tmCharSet', BYTE)]
+
+
+class OUTLINETEXTMETRIC(Structure):
+    _fields_ = [
+        ('otmSize', UINT), ('otmTextMetrics', TEXTMETRIC),
+        ('otmMysteryBytes', BYTE), ('otmPanoseNumber', PANOSE),
+        ('otmMysteryByte', BYTE),
+        ('otmfsSelection', UINT), ('otmfsType', UINT),
+        ('otmsCharSlopeRise', INT), ('otmsCharSlopeRun', INT),
+        ('otmItalicAngle', INT), ('otmEMSquare', UINT), ('otmAscent', INT),
+        ('otmDescent', INT), ('otmLineGap', UINT), ('otmsCapEmHeight', UINT),
+        ('otmsXHeight', UINT), ('otmrcFontBox', RECT), ('otmMacAscent', INT),
+        ('otmMacDescent', INT), ('otmMacLineGap', UINT),
+        ('otmusMinimumPPEM', UINT), ('otmptSubscriptSize', POINT),
+        ('otmptSubscriptOffset', POINT), ('otmptSuperscriptSize', POINT),
+        ('otmptSuperscriptOffset', POINT), ('otmsStrikeoutSize', UINT),
+        ('otmsStrikeoutPosition', INT), ('otmsUnderscoreSize', INT),
+        ('otmsUnderscorePosition', INT), ('otmpFamilyName', PSTR),
+        ('otmpFaceName', PSTR), ('otmpStyleName', PSTR),
+        ('otmpFullName', PSTR), ('junk', (BYTE) * 1024)]  # room for strs
+
+
+class LOGFONT(Structure):
+    _fields_ = [
+        ('lfHeight', LONG), ('lfWidth', LONG), ('lfEscapement', LONG),
+        ('lfOrientation', LONG), ('lfWeight', LONG), ('lfItalic', BYTE),
+        ('lfUnderline', BYTE), ('lfStrikeOut', BYTE), ('lfCharSet', BYTE),
+        ('lfOutPrecision', BYTE), ('lfClipPrecision', BYTE),
+        ('lfQuality', BYTE), ('lfPitchAndFamily', BYTE),
+        ('lfFaceName', (TCHAR * LF_FACESIZE))]
+
+
+gdi32 = windll.gdi32
+
+gdi32.CreateFontIndirectW.restype = HFONT
+gdi32.CreateFontIndirectW.argtypes = [POINTER(LOGFONT)]
+
+gdi32.SelectObject.restype = HGDIOBJ
+gdi32.SelectObject.argtypes = [HDC, HGDIOBJ]
+
+gdi32.SetGraphicsMode.restype = INT
+gdi32.SetGraphicsMode.argtypes = [HDC, INT]
+
+gdi32.GetTextMetricsW.restype = BOOL
+gdi32.GetTextMetricsW.argtypes = [HDC, POINTER(TEXTMETRIC)]
+
+FONTENUMPROC = WINFUNCTYPE(INT, POINTER(LOGFONT), POINTER(TEXTMETRIC),
+                           DWORD, c_void_p)
+gdi32.EnumFontFamiliesExW.restype = INT
+gdi32.EnumFontFamiliesExW.argtypes = [HDC, POINTER(LOGFONT),
+                                      FONTENUMPROC, LPARAM, DWORD]
+
+gdi32.GetOutlineTextMetricsW.restype = UINT
+gdi32.GetOutlineTextMetricsW.argtypes = [HDC, UINT,
+                                         POINTER(OUTLINETEXTMETRIC)]
+
+user32 = windll.user32
+
+user32.GetDC.restype = HDC  # HDC
+user32.GetDC.argtypes = [UINT32]  # HWND
+
+
+# gdiplus
+
+class GdiplusStartupInput(Structure):
+    _fields_ = [
+        ('GdiplusVersion', UINT32), ('DebugEventCallback', c_void_p),
+        ('SuppressBackgroundThread', BOOL), ('SuppressExternalCodecs', BOOL)]
+
+
+class GdiplusStartupOutput(Structure):
+    _fields = [('NotificationHookProc', c_void_p),
+               ('NotificationUnhookProc', c_void_p)]
+
+gdiplus = windll.gdiplus
+
+gdiplus.GdipCreateFontFamilyFromName.restype = c_int
+gdiplus.GdipCreateFontFamilyFromName.argtypes = [c_wchar_p, c_void_p, c_void_p]
+
+gdiplus.GdipNewPrivateFontCollection.restype = c_int
+gdiplus.GdipNewPrivateFontCollection.argtypes = [c_void_p]
+
+gdiplus.GdipPrivateAddFontFile.restype = c_int
+gdiplus.GdipPrivateAddFontFile.argtypes = [c_void_p, c_wchar_p]
+
+gdiplus.GdipGetFamilyName.restype = c_int
+gdiplus.GdipGetFamilyName.argtypes = [c_void_p, c_wchar_p, c_int]
+
+
+def gdiplus_init():
+    token = c_ulong()
+    startup_in = GdiplusStartupInput()
+    startup_in.GdiplusVersion = 1
+    startup_out = GdiplusStartupOutput()
+    gdiplus.GdiplusStartup(byref(token), byref(startup_in), byref(startup_out))
+    atexit.register(partial(gdiplus.GdiplusShutdown, token))
+
+gdiplus_init()
diff --git a/vispy/ext/glfw.py b/vispy/ext/glfw.py
new file mode 100644
index 0000000..a353484
--- /dev/null
+++ b/vispy/ext/glfw.py
@@ -0,0 +1,641 @@
+# -*- coding: utf-8 -*-
+
+# -----------------------------------------------------------------------------
+#  GLFW - An OpenGL framework
+#  API version: 3.0.1
+#  WWW:         http://www.glfw.org/
+#  ----------------------------------------------------------------------------
+#  Copyright (c) 2002-2006 Marcus Geelnard
+#  Copyright (c) 2006-2010 Camilla Berglund
+#
+#  Python bindings - Copyright (c) 2013 Nicolas P. Rougier
+#
+#  This software is provided 'as-is', without any express or implied
+#  warranty. In no event will the authors be held liable for any damages
+#  arising from the use of this software.
+#
+#  Permission is granted to anyone to use this software for any purpose,
+#  including commercial applications, and to alter it and redistribute it
+#  freely, subject to the following restrictions:
+#
+#  1. The origin of this software must not be misrepresented; you must not
+#     claim that you wrote the original software. If you use this software
+#     in a product, an acknowledgment in the product documentation would
+#     be appreciated but is not required.
+#
+#  2. Altered source versions must be plainly marked as such, and must not
+#     be misrepresented as being the original software.
+#
+#  3. This notice may not be removed or altered from any source
+#     distribution.
+#
+# -----------------------------------------------------------------------------
+
+# NOTE:
+# This source has been modified from its original form by the vispy dev team
+
+import os
+import ctypes.util
+from ctypes import (Structure, POINTER, CFUNCTYPE, byref, c_char_p, c_int,
+                    c_uint, c_double, c_float, c_ushort)
+
+
+_glfw_file = None
+
+# First if there is an environment variable pointing to the library
+if 'GLFW_LIBRARY' in os.environ:
+    if os.path.exists(os.environ['GLFW_LIBRARY']):
+        _glfw_file = os.path.realpath(os.environ['GLFW_LIBRARY'])
+
+# Else, try to find it
+if _glfw_file is None:
+    order = ['glfw3', 'glfw']
+    for check in order:
+        _glfw_file = ctypes.util.find_library(check)
+        if _glfw_file is not None:
+            break
+
+# Else, we failed and exit
+if _glfw_file is None:
+    raise OSError('GLFW library not found')
+
+# Load it
+_glfw = ctypes.CDLL(_glfw_file)
+
+
+# Ensure it's new enough
+def glfwGetVersion():
+    major, minor, rev = c_int(0), c_int(0), c_int(0)
+    _glfw.glfwGetVersion(byref(major), byref(minor), byref(rev))
+    return major.value, minor.value, rev.value
+
+version = glfwGetVersion()
+
+if version[0] != 3:
+    version = '.'.join([str(v) for v in version])
+    raise OSError('Need GLFW v3, found %s' % version)
+
+
+# --- Version -----------------------------------------------------------------
+GLFW_VERSION_MAJOR      = version[0]
+GLFW_VERSION_MINOR      = version[1]
+GLFW_VERSION_REVISION   = version[2]
+__version__ = GLFW_VERSION_MAJOR, GLFW_VERSION_MINOR, GLFW_VERSION_REVISION
+
+# --- Input handling definitions ----------------------------------------------
+GLFW_RELEASE            = 0
+GLFW_PRESS              = 1
+GLFW_REPEAT             = 2
+
+# --- Keys --------------------------------------------------------------------
+
+# --- The unknown key ---------------------------------------------------------
+GLFW_KEY_UNKNOWN          = -1
+
+# --- Printable keys ----------------------------------------------------------
+GLFW_KEY_SPACE            = 32
+GLFW_KEY_APOSTROPHE       = 39 # ''
+GLFW_KEY_COMMA            = 44 # ,
+GLFW_KEY_MINUS            = 45 # -
+GLFW_KEY_PERIOD           = 46 # .
+GLFW_KEY_SLASH            = 47 # /
+GLFW_KEY_0                = 48
+GLFW_KEY_1                = 49
+GLFW_KEY_2                = 50
+GLFW_KEY_3                = 51
+GLFW_KEY_4                = 52
+GLFW_KEY_5                = 53
+GLFW_KEY_6                = 54
+GLFW_KEY_7                = 55
+GLFW_KEY_8                = 56
+GLFW_KEY_9                = 57
+GLFW_KEY_SEMICOLON        = 59 # ;
+GLFW_KEY_EQUAL            = 61 # =
+GLFW_KEY_A                = 65
+GLFW_KEY_B                = 66
+GLFW_KEY_C                = 67
+GLFW_KEY_D                = 68
+GLFW_KEY_E                = 69
+GLFW_KEY_F                = 70
+GLFW_KEY_G                = 71
+GLFW_KEY_H                = 72
+GLFW_KEY_I                = 73
+GLFW_KEY_J                = 74
+GLFW_KEY_K                = 75
+GLFW_KEY_L                = 76
+GLFW_KEY_M                = 77
+GLFW_KEY_N                = 78
+GLFW_KEY_O                = 79
+GLFW_KEY_P                = 80
+GLFW_KEY_Q                = 81
+GLFW_KEY_R                = 82
+GLFW_KEY_S                = 83
+GLFW_KEY_T                = 84
+GLFW_KEY_U                = 85
+GLFW_KEY_V                = 86
+GLFW_KEY_W                = 87
+GLFW_KEY_X                = 88
+GLFW_KEY_Y                = 89
+GLFW_KEY_Z                = 90
+GLFW_KEY_LEFT_BRACKET     = 91  # [
+GLFW_KEY_BACKSLASH        = 92  # \
+GLFW_KEY_RIGHT_BRACKET    = 93  # ]
+GLFW_KEY_GRAVE_ACCENT     = 96  # `
+GLFW_KEY_WORLD_1          = 161 # non-US #1
+GLFW_KEY_WORLD_2          = 162 # non-US #2
+
+# --- Function keys -----------------------------------------------------------
+GLFW_KEY_ESCAPE           = 256
+GLFW_KEY_ENTER            = 257
+GLFW_KEY_TAB              = 258
+GLFW_KEY_BACKSPACE        = 259
+GLFW_KEY_INSERT           = 260
+GLFW_KEY_DELETE           = 261
+GLFW_KEY_RIGHT            = 262
+GLFW_KEY_LEFT             = 263
+GLFW_KEY_DOWN             = 264
+GLFW_KEY_UP               = 265
+GLFW_KEY_PAGE_UP          = 266
+GLFW_KEY_PAGE_DOWN        = 267
+GLFW_KEY_HOME             = 268
+GLFW_KEY_END              = 269
+GLFW_KEY_CAPS_LOCK        = 280
+GLFW_KEY_SCROLL_LOCK      = 281
+GLFW_KEY_NUM_LOCK         = 282
+GLFW_KEY_PRINT_SCREEN     = 283
+GLFW_KEY_PAUSE            = 284
+GLFW_KEY_F1               = 290
+GLFW_KEY_F2               = 291
+GLFW_KEY_F3               = 292
+GLFW_KEY_F4               = 293
+GLFW_KEY_F5               = 294
+GLFW_KEY_F6               = 295
+GLFW_KEY_F7               = 296
+GLFW_KEY_F8               = 297
+GLFW_KEY_F9               = 298
+GLFW_KEY_F10              = 299
+GLFW_KEY_F11              = 300
+GLFW_KEY_F12              = 301
+GLFW_KEY_F13              = 302
+GLFW_KEY_F14              = 303
+GLFW_KEY_F15              = 304
+GLFW_KEY_F16              = 305
+GLFW_KEY_F17              = 306
+GLFW_KEY_F18              = 307
+GLFW_KEY_F19              = 308
+GLFW_KEY_F20              = 309
+GLFW_KEY_F21              = 310
+GLFW_KEY_F22              = 311
+GLFW_KEY_F23              = 312
+GLFW_KEY_F24              = 313
+GLFW_KEY_F25              = 314
+GLFW_KEY_KP_0             = 320
+GLFW_KEY_KP_1             = 321
+GLFW_KEY_KP_2             = 322
+GLFW_KEY_KP_3             = 323
+GLFW_KEY_KP_4             = 324
+GLFW_KEY_KP_5             = 325
+GLFW_KEY_KP_6             = 326
+GLFW_KEY_KP_7             = 327
+GLFW_KEY_KP_8             = 328
+GLFW_KEY_KP_9             = 329
+GLFW_KEY_KP_DECIMAL       = 330
+GLFW_KEY_KP_DIVIDE        = 331
+GLFW_KEY_KP_MULTIPLY      = 332
+GLFW_KEY_KP_SUBTRACT      = 333
+GLFW_KEY_KP_ADD           = 334
+GLFW_KEY_KP_ENTER         = 335
+GLFW_KEY_KP_EQUAL         = 336
+GLFW_KEY_LEFT_SHIFT       = 340
+GLFW_KEY_LEFT_CONTROL     = 341
+GLFW_KEY_LEFT_ALT         = 342
+GLFW_KEY_LEFT_SUPER       = 343
+GLFW_KEY_RIGHT_SHIFT      = 344
+GLFW_KEY_RIGHT_CONTROL    = 345
+GLFW_KEY_RIGHT_ALT        = 346
+GLFW_KEY_RIGHT_SUPER      = 347
+GLFW_KEY_MENU             = 348
+GLFW_KEY_LAST             = GLFW_KEY_MENU
+
+
+# --- Modifiers ---------------------------------------------------------------
+GLFW_MOD_SHIFT            = 0x0001
+GLFW_MOD_CONTROL          = 0x0002
+GLFW_MOD_ALT              = 0x0004
+GLFW_MOD_SUPER            = 0x0008
+
+# --- Mouse -------------------------------------------------------------------
+GLFW_MOUSE_BUTTON_1       = 0
+GLFW_MOUSE_BUTTON_2       = 1
+GLFW_MOUSE_BUTTON_3       = 2
+GLFW_MOUSE_BUTTON_4       = 3
+GLFW_MOUSE_BUTTON_5       = 4
+GLFW_MOUSE_BUTTON_6       = 5
+GLFW_MOUSE_BUTTON_7       = 6
+GLFW_MOUSE_BUTTON_8       = 7
+GLFW_MOUSE_BUTTON_LAST    = GLFW_MOUSE_BUTTON_8
+GLFW_MOUSE_BUTTON_LEFT    = GLFW_MOUSE_BUTTON_1
+GLFW_MOUSE_BUTTON_RIGHT   = GLFW_MOUSE_BUTTON_2
+GLFW_MOUSE_BUTTON_MIDDLE  = GLFW_MOUSE_BUTTON_3
+
+
+# --- Joystick ----------------------------------------------------------------
+GLFW_JOYSTICK_1           = 0
+GLFW_JOYSTICK_2           = 1
+GLFW_JOYSTICK_3           = 2
+GLFW_JOYSTICK_4           = 3
+GLFW_JOYSTICK_5           = 4
+GLFW_JOYSTICK_6           = 5
+GLFW_JOYSTICK_7           = 6
+GLFW_JOYSTICK_8           = 7
+GLFW_JOYSTICK_9           = 8
+GLFW_JOYSTICK_10          = 9
+GLFW_JOYSTICK_11          = 10
+GLFW_JOYSTICK_12          = 11
+GLFW_JOYSTICK_13          = 12
+GLFW_JOYSTICK_14          = 13
+GLFW_JOYSTICK_15          = 14
+GLFW_JOYSTICK_16          = 15
+GLFW_JOYSTICK_LAST        = GLFW_JOYSTICK_16
+
+
+# --- Error codes -------------------------------------------------------------
+GLFW_NOT_INITIALIZED        = 0x00010001
+GLFW_NO_CURRENT_CONTEXT     = 0x00010002
+GLFW_INVALID_ENUM           = 0x00010003
+GLFW_INVALID_VALUE          = 0x00010004
+GLFW_OUT_OF_MEMORY          = 0x00010005
+GLFW_API_UNAVAILABLE        = 0x00010006
+GLFW_VERSION_UNAVAILABLE    = 0x00010007
+GLFW_PLATFORM_ERROR         = 0x00010008
+GLFW_FORMAT_UNAVAILABLE     = 0x00010009
+
+# ---
+GLFW_FOCUSED                = 0x00020001
+GLFW_ICONIFIED              = 0x00020002
+GLFW_RESIZABLE              = 0x00020003
+GLFW_VISIBLE                = 0x00020004
+GLFW_DECORATED              = 0x00020005
+
+# ---
+GLFW_RED_BITS               = 0x00021001
+GLFW_GREEN_BITS             = 0x00021002
+GLFW_BLUE_BITS              = 0x00021003
+GLFW_ALPHA_BITS             = 0x00021004
+GLFW_DEPTH_BITS             = 0x00021005
+GLFW_STENCIL_BITS           = 0x00021006
+GLFW_ACCUM_RED_BITS         = 0x00021007
+GLFW_ACCUM_GREEN_BITS       = 0x00021008
+GLFW_ACCUM_BLUE_BITS        = 0x00021009
+GLFW_ACCUM_ALPHA_BITS       = 0x0002100A
+GLFW_AUX_BUFFERS            = 0x0002100B
+GLFW_STEREO                 = 0x0002100C
+GLFW_SAMPLES                = 0x0002100D
+GLFW_SRGB_CAPABLE           = 0x0002100E
+GLFW_REFRESH_RATE           = 0x0002100F
+
+# ---
+GLFW_CLIENT_API             = 0x00022001
+GLFW_CONTEXT_VERSION_MAJOR  = 0x00022002
+GLFW_CONTEXT_VERSION_MINOR  = 0x00022003
+GLFW_CONTEXT_REVISION       = 0x00022004
+GLFW_CONTEXT_ROBUSTNESS     = 0x00022005
+GLFW_OPENGL_FORWARD_COMPAT  = 0x00022006
+GLFW_OPENGL_DEBUG_CONTEXT   = 0x00022007
+GLFW_OPENGL_PROFILE         = 0x00022008
+
+# ---
+GLFW_OPENGL_API             = 0x00030001
+GLFW_OPENGL_ES_API          = 0x00030002
+
+# ---
+GLFW_NO_ROBUSTNESS          =          0
+GLFW_NO_RESET_NOTIFICATION  = 0x00031001
+GLFW_LOSE_CONTEXT_ON_RESET  = 0x00031002
+
+# ---
+GLFW_OPENGL_ANY_PROFILE     =          0
+GLFW_OPENGL_CORE_PROFILE    = 0x00032001
+GLFW_OPENGL_COMPAT_PROFILE  = 0x00032002
+
+# ---
+GLFW_CURSOR                 = 0x00033001
+GLFW_STICKY_KEYS            = 0x00033002
+GLFW_STICKY_MOUSE_BUTTONS   = 0x00033003
+
+# ---
+GLFW_CURSOR_NORMAL          = 0x00034001
+GLFW_CURSOR_HIDDEN          = 0x00034002
+GLFW_CURSOR_DISABLED        = 0x00034003
+
+# ---
+GLFW_CONNECTED              = 0x00040001
+GLFW_DISCONNECTED           = 0x00040002
+
+
+# --- Structures --------------------------------------------------------------
+class GLFWvidmode(Structure):
+    _fields_ = [ ('width',       c_int),
+                 ('height',      c_int),
+                 ('redBits',     c_int),
+                 ('greenBits',   c_int),
+                 ('blueBits',    c_int),
+                 ('refreshRate', c_int) ]
+
+class GLFWgammaramp(Structure):
+    _fields_ = [ ('red',     POINTER(c_ushort)),
+                 ('green',   POINTER(c_ushort)),
+                 ('blue',    POINTER(c_ushort)),
+                 ('size',    c_int) ]
+
+class GLFWwindow(Structure): pass
+class GLFWmonitor(Structure): pass
+
+# --- Callbacks ---------------------------------------------------------------
+errorfun           = CFUNCTYPE(None, c_int, c_char_p)
+windowposfun       = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int)
+windowsizefun      = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int)
+windowclosefun     = CFUNCTYPE(None, POINTER(GLFWwindow))
+windowrefreshfun   = CFUNCTYPE(None, POINTER(GLFWwindow))
+windowfocusfun     = CFUNCTYPE(None, POINTER(GLFWwindow), c_int)
+windowiconifyfun   = CFUNCTYPE(None, POINTER(GLFWwindow), c_int)
+framebuffersizefun = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int)
+mousebuttonfun     = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int, c_int)
+cursorposfun       = CFUNCTYPE(None, POINTER(GLFWwindow), c_double, c_double)
+cursorenterfun     = CFUNCTYPE(None, POINTER(GLFWwindow), c_int)
+scrollfun          = CFUNCTYPE(None, POINTER(GLFWwindow), c_double, c_double)
+keyfun             = CFUNCTYPE(None, POINTER(GLFWwindow), c_int, c_int, c_int, c_int)
+charfun            = CFUNCTYPE(None, POINTER(GLFWwindow), c_uint)
+monitorfun         = CFUNCTYPE(None, POINTER(GLFWmonitor), c_int)
+
+# --- Init --------------------------------------------------------------------
+glfwInit                        = _glfw.glfwInit
+glfwTerminate                   = _glfw.glfwTerminate
+#glfwGetVersion                 = _glfw.glfwGetVersion
+
+# --- Error -------------------------------------------------------------------
+#glfwSetErrorCallback            = _glfw.glfwSetErrorCallback
+
+# --- Monitor -----------------------------------------------------------------
+# glfwGetMonitors                 = _glfw.glfwGetMonitors
+# glfwGetMonitors.restype         = POINTER(GLFWmonitor)
+glfwGetPrimaryMonitor           = _glfw.glfwGetPrimaryMonitor
+# glfwGetMonitorPos               = _glfw.glfwGetMonitorPos
+# glfwGetMonitorPhysicalSize      = _glfw.glfwGetMonitorPhysicalSize
+glfwGetMonitorName              = _glfw.glfwGetMonitorName
+glfwGetMonitorName.restype = c_char_p
+# glfwSetMonitorCallback          = _glfw.glfwSetMonitorCallback
+# glfwGetVideoModes               = _glfw.glfwGetVideoModes
+# glfwGetVideoMode                = _glfw.glfwGetVideoMode
+
+# --- Gama --------------------------------------------------------------------
+glfwSetGamma                   = _glfw.glfwSetGamma
+# glfwGetGammaRamp               = _glfw.glfwGetGammaRamp
+# glfwSetGammaRamp               = _glfw.glfwSetGammaRamp
+
+# --- Window ------------------------------------------------------------------
+glfwDefaultWindowHints         = _glfw.glfwDefaultWindowHints
+glfwWindowHint                 = _glfw.glfwWindowHint
+# glfwCreateWindow              = _glfw.glfwCreateWindow
+# glfwDestroyWindow              = _glfw.glfwDestroyWindow
+glfwWindowShouldClose          = _glfw.glfwWindowShouldClose
+glfwSetWindowShouldClose       = _glfw.glfwSetWindowShouldClose
+glfwSetWindowTitle             = _glfw.glfwSetWindowTitle
+# glfwGetWindowPos              = _glfw.glfwGetWindowPos
+glfwSetWindowPos               = _glfw.glfwSetWindowPos
+# glfwGetWindowSize             = _glfw.glfwGetWindowSize
+glfwSetWindowSize              = _glfw.glfwSetWindowSize
+# glfwGetFramebufferSize        = _glfw.glfwGetFramebufferSize
+glfwIconifyWindow              = _glfw.glfwIconifyWindow
+glfwRestoreWindow              = _glfw.glfwRestoreWindow
+glfwShowWindow                 = _glfw.glfwShowWindow
+glfwHideWindow                 = _glfw.glfwHideWindow
+glfwGetWindowMonitor           = _glfw.glfwGetWindowMonitor
+glfwGetWindowAttrib            = _glfw.glfwGetWindowAttrib
+glfwSetWindowUserPointer       = _glfw.glfwSetWindowUserPointer
+glfwGetWindowUserPointer       = _glfw.glfwGetWindowUserPointer
+# glfwSetWindowPosCallback       = _glfw.glfwSetWindowPosCallback
+# glfwSetWindowSizeCallback      = _glfw.glfwSetWindowSizeCallback
+# glfwSetWindowCloseCallback     = _glfw.glfwSetWindowCloseCallback
+# glfwSetWindowRefreshCallback   = _glfw.glfwSetWindowRefreshCallback
+# glfwSetWindowFocusCallback     = _glfw.glfwSetWindowFocusCallback
+# glfwSetWindowIconifyCallback   = _glfw.glfwSetWindowIconifyCallback
+# glfwSetFramebufferSizeCallback = _glfw.glfwSetFramebufferSizeCallback
+glfwPollEvents                 = _glfw.glfwPollEvents
+glfwWaitEvents                 = _glfw.glfwWaitEvents
+
+# --- Input -------------------------------------------------------------------
+glfwGetInputMode               = _glfw.glfwGetInputMode
+glfwSetInputMode               = _glfw.glfwSetInputMode
+glfwGetKey                     = _glfw.glfwGetKey
+glfwGetMouseButton             = _glfw.glfwGetMouseButton
+# glfwGetCursorPos               = _glfw.glfwGetCursorPos
+glfwSetCursorPos               = _glfw.glfwSetCursorPos
+# glfwSetKeyCallback             = _glfw.glfwSetKeyCallback
+# glfwSetCharCallback            = _glfw.glfwSetCharCallback
+# glfwSetMouseButtonCallback     = _glfw.glfwSetMouseButtonCallback
+# glfwSetCursorPosCallback       = _glfw.glfwSetCursorPosCallback
+# glfwSetCursorEnterCallback     = _glfw.glfwSetCursorEnterCallback
+# glfwSetScrollCallback          = _glfw.glfwSetScrollCallback
+glfwJoystickPresent            = _glfw.glfwJoystickPresent
+# glfwGetJoystickAxes            = _glfw.glfwGetJoystickAxes
+# glfwGetJoystickButtons         = _glfw.glfwGetJoystickButtons
+glfwGetJoystickName            = _glfw.glfwGetJoystickName
+glfwGetJoystickName.restype = c_char_p
+
+# --- Clipboard ---------------------------------------------------------------
+glfwSetClipboardString         = _glfw.glfwSetClipboardString
+glfwGetClipboardString         = _glfw.glfwGetClipboardString
+glfwGetClipboardString.restype = c_char_p
+
+# --- Timer -------------------------------------------------------------------
+glfwGetTime                    = _glfw.glfwGetTime
+glfwGetTime.restype = c_double
+glfwSetTime                    = _glfw.glfwSetTime
+
+# --- Context -----------------------------------------------------------------
+glfwMakeContextCurrent         = _glfw.glfwMakeContextCurrent
+glfwGetCurrentContext          = _glfw.glfwGetCurrentContext
+glfwSwapBuffers                = _glfw.glfwSwapBuffers
+glfwSwapInterval               = _glfw.glfwSwapInterval
+glfwExtensionSupported         = _glfw.glfwExtensionSupported
+glfwGetProcAddress             = _glfw.glfwGetProcAddress
+
+
+# --- Pythonizer --------------------------------------------------------------
+
+# This keeps track of current windows
+__windows__ = []
+__destroyed__ = []
+
+# This is to prevent garbage collection on callbacks
+__c_callbacks__ = {}
+__py_callbacks__ = {}
+
+
+def glfwCreateWindow(width=640, height=480, title="GLFW Window",
+                     monitor=None, share=None):
+    _glfw.glfwCreateWindow.restype = POINTER(GLFWwindow)
+    window = _glfw.glfwCreateWindow(int(width), int(height),
+                                    title.encode('ASCII'), monitor, share)
+    assert window not in __windows__
+    __windows__.append(window)
+    __destroyed__.append(False)
+    index = __windows__.index(window)
+    __c_callbacks__[index] = {}
+    __py_callbacks__[index] = { 'errorfun'           : None,
+                                'monitorfun'         : None,
+                                'windowposfun'       : None,
+                                'windowsizefun'      : None,
+                                'windowclosefun'     : None,
+                                'windowrefreshfun'   : None,
+                                'windowfocusfun'     : None,
+                                'windowiconifyfun'   : None,
+                                'framebuffersizefun' : None,
+                                'keyfun'             : None,
+                                'charfun'            : None,
+                                'mousebuttonfun'     : None,
+                                'cursorposfun'       : None,
+                                'cursorenterfun'     : None,
+                                'scrollfun'          : None }
+    return window
+
+
+def glfwDestroyWindow(window):
+    index = __windows__.index(window)
+    if not __destroyed__[index]:
+        __destroyed__[index] = True
+        # We do not delete window from the list (or it would impact numbering)
+        __windows__[index] = None
+        _glfw.glfwDestroyWindow(window)
+        del __c_callbacks__[index]
+        del __py_callbacks__[index]
+
+
+def glfwGetWindowPos(window):
+    xpos, ypos = c_int(0), c_int(0)
+    _glfw.glfwGetWindowPos(window, byref(xpos), byref(ypos))
+    return xpos.value, ypos.value
+
+
+def glfwGetCursorPos(window):
+    xpos, ypos = c_double(0), c_double(0)
+    _glfw.glfwGetCursorPos(window, byref(xpos), byref(ypos))
+    return int(xpos.value), int(ypos.value)
+
+
+def glfwGetWindowSize(window):
+    width, height = c_int(0), c_int(0)
+    _glfw.glfwGetWindowSize(window, byref(width), byref(height))
+    return width.value, height.value
+
+
+def glfwGetFramebufferSize(window):
+    width, height = c_int(0), c_int(0)
+    _glfw.glfwGetFramebufferSize(window, byref(width), byref(height))
+    return width.value, height.value
+
+
+def glfwGetMonitors():
+    count = c_int(0)
+    _glfw.glfwGetMonitors.restype = POINTER(POINTER(GLFWmonitor))
+    c_monitors = _glfw.glfwGetMonitors( byref(count) )
+    return [c_monitors[i] for i in range(count.value)]
+
+
+def glfwGetVideoModes(monitor):
+    count = c_int(0)
+    _glfw.glfwGetVideoModes.restype = POINTER(GLFWvidmode)
+    c_modes = _glfw.glfwGetVideoModes( monitor, byref(count) )
+    modes = []
+    for i in range(count.value):
+        modes.append( (c_modes[i].width,
+                       c_modes[i].height,
+                       c_modes[i].redBits,
+                       c_modes[i].blueBits,
+                       c_modes[i].greenBits,
+                       c_modes[i].refreshRate ) )
+    return modes
+
+
+def glfwGetMonitorPos(monitor):
+    xpos, ypos = c_int(0), c_int(0)
+    _glfw.glfwGetMonitorPos(monitor, byref(xpos), byref(ypos))
+    return xpos.value, ypos.value
+
+
+def glfwGetMonitorPhysicalSize(monitor):
+    width, height = c_int(0), c_int(0)
+    _glfw.glfwGetMonitorPhysicalSize(monitor, byref(width), byref(height))
+    return width.value, height.value
+
+
+def glfwGetVideoMode(monitor):
+    _glfw.glfwGetVideoMode.restype = POINTER(GLFWvidmode)
+    c_modes = _glfw.glfwGetVideoModes(monitor)
+    return (c_modes.width,
+            c_modes.height,
+            c_modes.redBits,
+            c_modes.blueBits,
+            c_modes.greenBits,
+            c_modes.refreshRate )
+
+
+def GetGammaRamp(monitor):
+    _glfw.glfwGetGammaRamp.restype = POINTER(GLFWgammaramp)
+    c_gamma = _glfw.glfwGetGammaRamp(monitor).contents
+    gamma = {'red':[], 'green':[], 'blue':[]}
+    if c_gamma:
+        for i in range(c_gamma.size):
+            gamma['red'].append(c_gamma.red[i])
+            gamma['green'].append(c_gamma.green[i])
+            gamma['blue'].append(c_gamma.blue[i])
+    return gamma
+
+
+def glfwGetJoystickAxes(joy):
+    count = c_int(0)
+    _glfw.glfwGetJoystickAxes.restype = POINTER(c_float)
+    c_axes = _glfw.glfwGetJoystickAxes(joy, byref(count))
+    axes = [c_axes[i].value for i in range(count)]
+    return axes
+
+
+def glfwGetJoystickButtons(joy):
+    count = c_int(0)
+    _glfw.glfwGetJoystickButtons.restype = POINTER(c_int)
+    c_buttons = _glfw.glfwGetJoystickButtons(joy, byref(count))
+    buttons = [c_buttons[i].value for i in range(count)]
+    return buttons
+
+
+# --- Callbacks ---------------------------------------------------------------
+
+def __callback__(name):
+    callback = 'glfwSet%sCallback' % name
+    fun      = '%sfun' % name.lower()
+    code = """
+def %(callback)s(window, callback = None):
+    index = __windows__.index(window)
+    old_callback = __py_callbacks__[index]['%(fun)s']
+    __py_callbacks__[index]['%(fun)s'] = callback
+    if callback: callback = %(fun)s(callback)
+    __c_callbacks__[index]['%(fun)s'] = callback
+    _glfw.%(callback)s(window, callback)
+    return old_callback""" % {'callback': callback, 'fun': fun}
+    return code
+
+exec(__callback__('Error'))
+exec(__callback__('Monitor'))
+exec(__callback__('WindowPos'))
+exec(__callback__('WindowSize'))
+exec(__callback__('WindowClose'))
+exec(__callback__('WindowRefresh'))
+exec(__callback__('WindowFocus'))
+exec(__callback__('WindowIconify'))
+exec(__callback__('FramebufferSize'))
+exec(__callback__('Key'))
+exec(__callback__('Char'))
+exec(__callback__('MouseButton'))
+exec(__callback__('CursorPos'))
+exec(__callback__('Scroll'))
diff --git a/vispy/ext/gzip_open.py b/vispy/ext/gzip_open.py
new file mode 100644
index 0000000..7002f4b
--- /dev/null
+++ b/vispy/ext/gzip_open.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+from gzip import GzipFile
+
+
+# Python < 2.7 doesn't have context handling
+class gzip_open(GzipFile):
+    def __enter__(self):
+        if hasattr(GzipFile, '__enter__'):
+            return GzipFile.__enter__(self)
+        else:
+            return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        if hasattr(GzipFile, '__exit__'):
+            return GzipFile.__exit__(self, exc_type, exc_value, traceback)
+        else:
+            return self.close()
diff --git a/vispy/ext/mplexporter.py b/vispy/ext/mplexporter.py
new file mode 100644
index 0000000..c116369
--- /dev/null
+++ b/vispy/ext/mplexporter.py
@@ -0,0 +1,693 @@
+"""
+Matplotlib Exporter
+===================
+This submodule contains tools for crawling a matplotlib figure and exporting
+relevant pieces to a renderer.
+
+Copyright (c) 2014, mpld3
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,  # noqa
+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  # noqa
+  list of conditions and the following disclaimer in the documentation and/or
+  other materials provided with the distribution.
+
+* Neither the name of the {organization} 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 HOLDER OR CONTRIBUTORS BE LIABLE FOR  # noqa
+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.
+
+"""
+import warnings
+import io
+from . import mplutils as utils
+
+import matplotlib
+from matplotlib import transforms
+
+
+class Exporter(object):
+    """Matplotlib Exporter
+
+    Parameters
+    ----------
+    renderer : Renderer object
+        The renderer object called by the exporter to create a figure
+        visualization.  See mplexporter.Renderer for information on the
+        methods which should be defined within the renderer.
+    close_mpl : bool
+        If True (default), close the matplotlib figure as it is rendered. This
+        is useful for when the exporter is used within the notebook, or with
+        an interactive matplotlib backend.
+    """
+
+    def __init__(self, renderer, close_mpl=True):
+        self.close_mpl = close_mpl
+        self.renderer = renderer
+
+    def run(self, fig):
+        """
+        Run the exporter on the given figure
+
+        Parmeters
+        ---------
+        fig : matplotlib.Figure instance
+            The figure to export
+        """
+        # Calling savefig executes the draw() command, putting elements
+        # in the correct place.
+        fig.savefig(io.BytesIO(), format='png', dpi=fig.dpi)
+        if self.close_mpl:
+            import matplotlib.pyplot as plt
+            plt.close(fig)
+        self.crawl_fig(fig)
+
+    @staticmethod
+    def process_transform(transform, ax=None, data=None, return_trans=False,
+                          force_trans=None):
+        """Process the transform and convert data to figure or data coordinates
+
+        Parameters
+        ----------
+        transform : matplotlib Transform object
+            The transform applied to the data
+        ax : matplotlib Axes object (optional)
+            The axes the data is associated with
+        data : ndarray (optional)
+            The array of data to be transformed.
+        return_trans : bool (optional)
+            If true, return the final transform of the data
+        force_trans : matplotlib.transform instance (optional)
+            If supplied, first force the data to this transform
+
+        Returns
+        -------
+        code : string
+            Code is either "data", "axes", "figure", or "display", indicating
+            the type of coordinates output.
+        transform : matplotlib transform
+            the transform used to map input data to output data.
+            Returned only if return_trans is True
+        new_data : ndarray
+            Data transformed to match the given coordinate code.
+            Returned only if data is specified
+        """
+        if isinstance(transform, transforms.BlendedGenericTransform):
+            warnings.warn("Blended transforms not yet supported. "
+                          "Zoom behavior may not work as expected.")
+
+        if force_trans is not None:
+            if data is not None:
+                data = (transform - force_trans).transform(data)
+            transform = force_trans
+
+        code = "display"
+        if ax is not None:
+            for (c, trans) in [("data", ax.transData),
+                               ("axes", ax.transAxes),
+                               ("figure", ax.figure.transFigure),
+                               ("display", transforms.IdentityTransform())]:
+                if transform.contains_branch(trans):
+                    code, transform = (c, transform - trans)
+                    break
+
+        if data is not None:
+            if return_trans:
+                return code, transform.transform(data), transform
+            else:
+                return code, transform.transform(data)
+        else:
+            if return_trans:
+                return code, transform
+            else:
+                return code
+
+    def crawl_fig(self, fig):
+        """Crawl the figure and process all axes"""
+        with self.renderer.draw_figure(fig=fig,
+                                       props=utils.get_figure_properties(fig)):
+            for ax in fig.axes:
+                self.crawl_ax(ax)
+
+    def crawl_ax(self, ax):
+        """Crawl the axes and process all elements within"""
+        with self.renderer.draw_axes(ax=ax,
+                                     props=utils.get_axes_properties(ax)):
+            for line in ax.lines:
+                self.draw_line(ax, line)
+            for text in ax.texts:
+                self.draw_text(ax, text)
+            for (text, ttp) in zip([ax.xaxis.label, ax.yaxis.label, ax.title],
+                                   ["xlabel", "ylabel", "title"]):
+                if(hasattr(text, 'get_text') and text.get_text()):
+                    self.draw_text(ax, text, force_trans=ax.transAxes,
+                                   text_type=ttp)
+            for artist in ax.artists:
+                # TODO: process other artists
+                if isinstance(artist, matplotlib.text.Text):
+                    self.draw_text(ax, artist)
+            for patch in ax.patches:
+                self.draw_patch(ax, patch)
+            for collection in ax.collections:
+                self.draw_collection(ax, collection)
+            for image in ax.images:
+                self.draw_image(ax, image)
+
+            legend = ax.get_legend()
+            if legend is not None:
+                props = utils.get_legend_properties(ax, legend)
+                with self.renderer.draw_legend(legend=legend, props=props):
+                    if props['visible']:
+                        self.crawl_legend(ax, legend)
+
+    def crawl_legend(self, ax, legend):
+        """
+        Recursively look through objects in legend children
+        """
+        legendElements = list(utils.iter_all_children(legend._legend_box,
+                                                      skipContainers=True))
+        legendElements.append(legend.legendPatch)
+        for child in legendElements:
+            # force a large zorder so it appears on top
+            child.set_zorder(1E6 + child.get_zorder())
+
+            try:
+                # What kind of object...
+                if isinstance(child, matplotlib.patches.Patch):
+                    self.draw_patch(ax, child, force_trans=ax.transAxes)
+                elif isinstance(child, matplotlib.text.Text):
+                    if not (child is legend.get_children()[-1]
+                            and child.get_text() == 'None'):
+                        self.draw_text(ax, child, force_trans=ax.transAxes)
+                elif isinstance(child, matplotlib.lines.Line2D):
+                    self.draw_line(ax, child, force_trans=ax.transAxes)
+                elif isinstance(child, matplotlib.collections.Collection):
+                    self.draw_collection(ax, child,
+                                         force_pathtrans=ax.transAxes)
+                else:
+                    warnings.warn("Legend element %s not impemented" % child)
+            except NotImplementedError:
+                warnings.warn("Legend element %s not impemented" % child)
+
+    def draw_line(self, ax, line, force_trans=None):
+        """Process a matplotlib line and call renderer.draw_line"""
+        coordinates, data = self.process_transform(line.get_transform(),
+                                                   ax, line.get_xydata(),
+                                                   force_trans=force_trans)
+        linestyle = utils.get_line_style(line)
+        if linestyle['dasharray'] is None:
+            linestyle = None
+        markerstyle = utils.get_marker_style(line)
+        if (markerstyle['marker'] in ['None', 'none', None]
+                or markerstyle['markerpath'][0].size == 0):
+            markerstyle = None
+        label = line.get_label()
+        if markerstyle or linestyle:
+            self.renderer.draw_marked_line(data=data, coordinates=coordinates,
+                                           linestyle=linestyle,
+                                           markerstyle=markerstyle,
+                                           label=label,
+                                           mplobj=line)
+
+    def draw_text(self, ax, text, force_trans=None, text_type=None):
+        """Process a matplotlib text object and call renderer.draw_text"""
+        content = text.get_text()
+        if content:
+            transform = text.get_transform()
+            position = text.get_position()
+            coords, position = self.process_transform(transform, ax,
+                                                      position,
+                                                      force_trans=force_trans)
+            style = utils.get_text_style(text)
+            self.renderer.draw_text(text=content, position=position,
+                                    coordinates=coords,
+                                    text_type=text_type,
+                                    style=style, mplobj=text)
+
+    def draw_patch(self, ax, patch, force_trans=None):
+        """Process a matplotlib patch object and call renderer.draw_path"""
+        vertices, pathcodes = utils.SVG_path(patch.get_path())
+        transform = patch.get_transform()
+        coordinates, vertices = self.process_transform(transform,
+                                                       ax, vertices,
+                                                       force_trans=force_trans)
+        linestyle = utils.get_path_style(patch, fill=patch.get_fill())
+        self.renderer.draw_path(data=vertices,
+                                coordinates=coordinates,
+                                pathcodes=pathcodes,
+                                style=linestyle,
+                                mplobj=patch)
+
+    def draw_collection(self, ax, collection,
+                        force_pathtrans=None,
+                        force_offsettrans=None):
+        """Process a matplotlib collection and call renderer.draw_collection"""
+        (transform, transOffset,
+         offsets, paths) = collection._prepare_points()
+
+        offset_coords, offsets = self.process_transform(
+            transOffset, ax, offsets, force_trans=force_offsettrans)
+        path_coords = self.process_transform(
+            transform, ax, force_trans=force_pathtrans)
+
+        processed_paths = [utils.SVG_path(path) for path in paths]
+        processed_paths = [(self.process_transform(
+            transform, ax, path[0], force_trans=force_pathtrans)[1], path[1])
+            for path in processed_paths]
+
+        path_transforms = collection.get_transforms()
+        try:
+            # matplotlib 1.3: path_transforms are transform objects.
+            # Convert them to numpy arrays.
+            path_transforms = [t.get_matrix() for t in path_transforms]
+        except AttributeError:
+            # matplotlib 1.4: path transforms are already numpy arrays.
+            pass
+
+        styles = {'linewidth': collection.get_linewidths(),
+                  'facecolor': collection.get_facecolors(),
+                  'edgecolor': collection.get_edgecolors(),
+                  'alpha': collection._alpha,
+                  'zorder': collection.get_zorder()}
+
+        offset_dict = {"data": "before",
+                       "screen": "after"}
+        offset_order = offset_dict[collection.get_offset_position()]
+
+        self.renderer.draw_path_collection(paths=processed_paths,
+                                           path_coordinates=path_coords,
+                                           path_transforms=path_transforms,
+                                           offsets=offsets,
+                                           offset_coordinates=offset_coords,
+                                           offset_order=offset_order,
+                                           styles=styles,
+                                           mplobj=collection)
+
+    def draw_image(self, ax, image):
+        """Process a matplotlib image object and call renderer.draw_image"""
+        self.renderer.draw_image(imdata=utils.image_to_base64(image),
+                                 extent=image.get_extent(),
+                                 coordinates="data",
+                                 style={"alpha": image.get_alpha(),
+                                        "zorder": image.get_zorder()},
+                                 mplobj=image)
+
+
+##############################################################################
+# Renderers/base.py
+
+import itertools
+from contextlib import contextmanager
+
+import numpy as np
+
+from . import _mpl_py3k_compat as py3k
+
+
+class Renderer(object):
+    @staticmethod
+    def ax_zoomable(ax):
+        return bool(ax and ax.get_navigate())
+
+    @staticmethod
+    def ax_has_xgrid(ax):
+        return bool(ax and ax.xaxis._gridOnMajor and ax.yaxis.get_gridlines())
+
+    @staticmethod
+    def ax_has_ygrid(ax):
+        return bool(ax and ax.yaxis._gridOnMajor and ax.yaxis.get_gridlines())
+
+    @property
+    def current_ax_zoomable(self):
+        return self.ax_zoomable(self._current_ax)
+
+    @property
+    def current_ax_has_xgrid(self):
+        return self.ax_has_xgrid(self._current_ax)
+
+    @property
+    def current_ax_has_ygrid(self):
+        return self.ax_has_ygrid(self._current_ax)
+
+    @contextmanager
+    def draw_figure(self, fig, props):
+        if hasattr(self, "_current_fig") and self._current_fig is not None:
+            warnings.warn("figure embedded in figure: something is wrong")
+        self._current_fig = fig
+        self._fig_props = props
+        self.open_figure(fig=fig, props=props)
+        yield
+        self.close_figure(fig=fig)
+        self._current_fig = None
+        self._fig_props = {}
+
+    @contextmanager
+    def draw_axes(self, ax, props):
+        if hasattr(self, "_current_ax") and self._current_ax is not None:
+            warnings.warn("axes embedded in axes: something is wrong")
+        self._current_ax = ax
+        self._ax_props = props
+        self.open_axes(ax=ax, props=props)
+        yield
+        self.close_axes(ax=ax)
+        self._current_ax = None
+        self._ax_props = {}
+
+    @contextmanager
+    def draw_legend(self, legend, props):
+        self._current_legend = legend
+        self._legend_props = props
+        self.open_legend(legend=legend, props=props)
+        yield
+        self.close_legend(legend=legend)
+        self._current_legend = None
+        self._legend_props = {}
+
+    # Following are the functions which should be overloaded in subclasses
+
+    def open_figure(self, fig, props):
+        """
+        Begin commands for a particular figure.
+
+        Parameters
+        ----------
+        fig : matplotlib.Figure
+            The Figure which will contain the ensuing axes and elements
+        props : dictionary
+            The dictionary of figure properties
+        """
+        pass
+
+    def close_figure(self, fig):
+        """
+        Finish commands for a particular figure.
+
+        Parameters
+        ----------
+        fig : matplotlib.Figure
+            The figure which is finished being drawn.
+        """
+        pass
+
+    def open_axes(self, ax, props):
+        """
+        Begin commands for a particular axes.
+
+        Parameters
+        ----------
+        ax : matplotlib.Axes
+            The Axes which will contain the ensuing axes and elements
+        props : dictionary
+            The dictionary of axes properties
+        """
+        pass
+
+    def close_axes(self, ax):
+        """
+        Finish commands for a particular axes.
+
+        Parameters
+        ----------
+        ax : matplotlib.Axes
+            The Axes which is finished being drawn.
+        """
+        pass
+
+    def open_legend(self, legend, props):
+        """
+        Beging commands for a particular legend.
+
+        Parameters
+        ----------
+        legend : matplotlib.legend.Legend
+                The Legend that will contain the ensuing elements
+        props : dictionary
+                The dictionary of legend properties
+        """
+        pass
+
+    def close_legend(self, legend):
+        """
+        Finish commands for a particular legend.
+
+        Parameters
+        ----------
+        legend : matplotlib.legend.Legend
+                The Legend which is finished being drawn
+        """
+        pass
+
+    def draw_marked_line(self, data, coordinates, linestyle, markerstyle,
+                         label, mplobj=None):
+        """Draw a line that also has markers.
+
+        If this isn't reimplemented by a renderer object, by default, it will
+        make a call to BOTH draw_line and draw_markers when both markerstyle
+        and linestyle are not None in the same Line2D object.
+
+        """
+        if linestyle is not None:
+            self.draw_line(data, coordinates, linestyle, label, mplobj)
+        if markerstyle is not None:
+            self.draw_markers(data, coordinates, markerstyle, label, mplobj)
+
+    def draw_line(self, data, coordinates, style, label, mplobj=None):
+        """
+        Draw a line. By default, draw the line via the draw_path() command.
+        Some renderers might wish to override this and provide more
+        fine-grained behavior.
+
+        In matplotlib, lines are generally created via the plt.plot() command,
+        though this command also can create marker collections.
+
+        Parameters
+        ----------
+        data : array_like
+            A shape (N, 2) array of datapoints.
+        coordinates : string
+            A string code, which should be either 'data' for data coordinates,
+            or 'figure' for figure (pixel) coordinates.
+        style : dictionary
+            a dictionary specifying the appearance of the line.
+        mplobj : matplotlib object
+            the matplotlib plot element which generated this line
+        """
+        pathcodes = ['M'] + (data.shape[0] - 1) * ['L']
+        pathstyle = dict(facecolor='none', **style)
+        pathstyle['edgecolor'] = pathstyle.pop('color')
+        pathstyle['edgewidth'] = pathstyle.pop('linewidth')
+        self.draw_path(data=data, coordinates=coordinates,
+                       pathcodes=pathcodes, style=pathstyle, mplobj=mplobj)
+
+    @staticmethod
+    def _iter_path_collection(paths, path_transforms, offsets, styles):
+        """Build an iterator over the elements of the path collection"""
+        N = max(len(paths), len(offsets))
+
+        if not path_transforms:
+            path_transforms = [np.eye(3)]
+
+        edgecolor = styles['edgecolor']
+        if np.size(edgecolor) == 0:
+            edgecolor = ['none']
+        facecolor = styles['facecolor']
+        if np.size(facecolor) == 0:
+            facecolor = ['none']
+
+        elements = [paths, path_transforms, offsets,
+                    edgecolor, styles['linewidth'], facecolor]
+
+        it = itertools
+        return it.islice(py3k.zip(*py3k.map(it.cycle, elements)), N)
+
+    def draw_path_collection(self, paths, path_coordinates, path_transforms,
+                             offsets, offset_coordinates, offset_order,
+                             styles, mplobj=None):
+        """
+        Draw a collection of paths. The paths, offsets, and styles are all
+        iterables, and the number of paths is max(len(paths), len(offsets)).
+
+        By default, this is implemented via multiple calls to the draw_path()
+        function. For efficiency, Renderers may choose to customize this
+        implementation.
+
+        Examples of path collections created by matplotlib are scatter plots,
+        histograms, contour plots, and many others.
+
+        Parameters
+        ----------
+        paths : list
+            list of tuples, where each tuple has two elements:
+            (data, pathcodes).  See draw_path() for a description of these.
+        path_coordinates: string
+            the coordinates code for the paths, which should be either
+            'data' for data coordinates, or 'figure' for figure (pixel)
+            coordinates.
+        path_transforms: array_like
+            an array of shape (*, 3, 3), giving a series of 2D Affine
+            transforms for the paths. These encode translations, rotations,
+            and scalings in the standard way.
+        offsets: array_like
+            An array of offsets of shape (N, 2)
+        offset_coordinates : string
+            the coordinates code for the offsets, which should be either
+            'data' for data coordinates, or 'figure' for figure (pixel)
+            coordinates.
+        offset_order : string
+            either "before" or "after". This specifies whether the offset
+            is applied before the path transform, or after.  The matplotlib
+            backend equivalent is "before"->"data", "after"->"screen".
+        styles: dictionary
+            A dictionary in which each value is a list of length N, containing
+            the style(s) for the paths.
+        mplobj : matplotlib object
+            the matplotlib plot element which generated this collection
+        """
+        if offset_order == "before":
+            raise NotImplementedError("offset before transform")
+
+        for tup in self._iter_path_collection(paths, path_transforms,
+                                              offsets, styles):
+            (path, path_transform, offset, ec, lw, fc) = tup
+            vertices, pathcodes = path
+            path_transform = transforms.Affine2D(path_transform)
+            vertices = path_transform.transform(vertices)
+            # This is a hack:
+            if path_coordinates == "figure":
+                path_coordinates = "points"
+            style = {"edgecolor": utils.color_to_hex(ec),
+                     "facecolor": utils.color_to_hex(fc),
+                     "edgewidth": lw,
+                     "dasharray": "10,0",
+                     "alpha": styles['alpha'],
+                     "zorder": styles['zorder']}
+            self.draw_path(data=vertices, coordinates=path_coordinates,
+                           pathcodes=pathcodes, style=style, offset=offset,
+                           offset_coordinates=offset_coordinates,
+                           mplobj=mplobj)
+
+    def draw_markers(self, data, coordinates, style, label, mplobj=None):
+        """
+        Draw a set of markers. By default, this is done by repeatedly
+        calling draw_path(), but renderers should generally overload
+        this method to provide a more efficient implementation.
+
+        In matplotlib, markers are created using the plt.plot() command.
+
+        Parameters
+        ----------
+        data : array_like
+            A shape (N, 2) array of datapoints.
+        coordinates : string
+            A string code, which should be either 'data' for data coordinates,
+            or 'figure' for figure (pixel) coordinates.
+        style : dictionary
+            a dictionary specifying the appearance of the markers.
+        mplobj : matplotlib object
+            the matplotlib plot element which generated this marker collection
+        """
+        vertices, pathcodes = style['markerpath']
+        pathstyle = dict((key, style[key]) for key in ['alpha', 'edgecolor',
+                                                       'facecolor', 'zorder',
+                                                       'edgewidth'])
+        pathstyle['dasharray'] = "10,0"
+        for vertex in data:
+            self.draw_path(data=vertices, coordinates="points",
+                           pathcodes=pathcodes, style=pathstyle,
+                           offset=vertex, offset_coordinates=coordinates,
+                           mplobj=mplobj)
+
+    def draw_text(self, text, position, coordinates, style,
+                  text_type=None, mplobj=None):
+        """
+        Draw text on the image.
+
+        Parameters
+        ----------
+        text : string
+            The text to draw
+        position : tuple
+            The (x, y) position of the text
+        coordinates : string
+            A string code, which should be either 'data' for data coordinates,
+            or 'figure' for figure (pixel) coordinates.
+        style : dictionary
+            a dictionary specifying the appearance of the text.
+        text_type : string or None
+            if specified, a type of text such as "xlabel", "ylabel", "title"
+        mplobj : matplotlib object
+            the matplotlib plot element which generated this text
+        """
+        raise NotImplementedError()
+
+    def draw_path(self, data, coordinates, pathcodes, style,
+                  offset=None, offset_coordinates="data", mplobj=None):
+        """
+        Draw a path.
+
+        In matplotlib, paths are created by filled regions, histograms,
+        contour plots, patches, etc.
+
+        Parameters
+        ----------
+        data : array_like
+            A shape (N, 2) array of datapoints.
+        coordinates : string
+            A string code, which should be either 'data' for data coordinates,
+            'figure' for figure (pixel) coordinates, or "points" for raw
+            point coordinates (useful in conjunction with offsets, below).
+        pathcodes : list
+            A list of single-character SVG pathcodes associated with the data.
+            Path codes are one of ['M', 'm', 'L', 'l', 'Q', 'q', 'T', 't',
+                                   'S', 's', 'C', 'c', 'Z', 'z']
+            See the SVG specification for details.  Note that some path codes
+            consume more than one datapoint (while 'Z' consumes none), so
+            in general, the length of the pathcodes list will not be the same
+            as that of the data array.
+        style : dictionary
+            a dictionary specifying the appearance of the line.
+        offset : list (optional)
+            the (x, y) offset of the path. If not given, no offset will
+            be used.
+        offset_coordinates : string (optional)
+            A string code, which should be either 'data' for data coordinates,
+            or 'figure' for figure (pixel) coordinates.
+        mplobj : matplotlib object
+            the matplotlib plot element which generated this path
+        """
+        raise NotImplementedError()
+
+    def draw_image(self, imdata, extent, coordinates, style, mplobj=None):
+        """
+        Draw an image.
+
+        Parameters
+        ----------
+        imdata : string
+            base64 encoded png representation of the image
+        extent : list
+            the axes extent of the image: [xmin, xmax, ymin, ymax]
+        coordinates: string
+            A string code, which should be either 'data' for data coordinates,
+            or 'figure' for figure (pixel) coordinates.
+        style : dictionary
+            a dictionary specifying the appearance of the image
+        mplobj : matplotlib object
+            the matplotlib plot object which generated this image
+        """
+        raise NotImplementedError()
diff --git a/vispy/ext/mplutils.py b/vispy/ext/mplutils.py
new file mode 100644
index 0000000..e998af6
--- /dev/null
+++ b/vispy/ext/mplutils.py
@@ -0,0 +1,353 @@
+"""
+Utility Routines for Working with Matplotlib Objects
+====================================================
+"""
+import itertools
+import io
+import base64
+
+import numpy as np
+
+import warnings
+
+import matplotlib
+from matplotlib.colors import colorConverter
+from matplotlib.path import Path
+from matplotlib.markers import MarkerStyle
+from matplotlib.transforms import Affine2D
+from matplotlib import ticker
+
+
+def color_to_hex(color):
+    """Convert matplotlib color code to hex color code"""
+    if color is None or colorConverter.to_rgba(color)[3] == 0:
+        return 'none'
+    else:
+        rgb = colorConverter.to_rgb(color)
+        return '#{0:02X}{1:02X}{2:02X}'.format(*(int(255 * c) for c in rgb))
+
+
+def _many_to_one(input_dict):
+    """Convert a many-to-one mapping to a one-to-one mapping"""
+    return dict((key, val)
+                for keys, val in input_dict.items()
+                for key in keys)
+
+LINESTYLES = _many_to_one({('solid', '-', (None, None)): 'none',
+                           ('dashed', '--'): "6,6",
+                           ('dotted', ':'): "2,2",
+                           ('dashdot', '-.'): "4,4,2,4",
+                           ('', ' ', 'None', 'none'): None})
+
+
+def get_dasharray(obj):
+    """Get an SVG dash array for the given matplotlib linestyle
+
+    Parameters
+    ----------
+    obj : matplotlib object
+        The matplotlib line or path object, which must have a get_linestyle()
+        method which returns a valid matplotlib line code
+
+    Returns
+    -------
+    dasharray : string
+        The HTML/SVG dasharray code associated with the object.
+    """
+    if obj.__dict__.get('_dashSeq', None) is not None:
+        return ','.join(map(str, obj._dashSeq))
+    else:
+        ls = obj.get_linestyle()
+        dasharray = LINESTYLES.get(ls, 'not found')
+        if dasharray == 'not found':
+            warnings.warn("line style '{0}' not understood: "
+                          "defaulting to solid line.".format(ls))
+            dasharray = LINESTYLES['solid']
+        return dasharray
+
+
+PATH_DICT = {Path.LINETO: 'L',
+             Path.MOVETO: 'M',
+             Path.CURVE3: 'S',
+             Path.CURVE4: 'C',
+             Path.CLOSEPOLY: 'Z'}
+
+
+def SVG_path(path, transform=None, simplify=False):
+    """Construct the vertices and SVG codes for the path
+
+    Parameters
+    ----------
+    path : matplotlib.Path object
+
+    transform : matplotlib transform (optional)
+        if specified, the path will be transformed before computing the output.
+
+    Returns
+    -------
+    vertices : array
+        The shape (M, 2) array of vertices of the Path. Note that some Path
+        codes require multiple vertices, so the length of these vertices may
+        be longer than the list of path codes.
+    path_codes : list
+        A length N list of single-character path codes, N <= M. Each code is
+        a single character, in ['L','M','S','C','Z']. See the standard SVG
+        path specification for a description of these.
+    """
+    if transform is not None:
+        path = path.transformed(transform)
+
+    vc_tuples = [(vertices if path_code != Path.CLOSEPOLY else [],
+                  PATH_DICT[path_code])
+                 for (vertices, path_code)
+                 in path.iter_segments(simplify=simplify)]
+
+    if not vc_tuples:
+        # empty path is a special case
+        return np.zeros((0, 2)), []
+    else:
+        vertices, codes = zip(*vc_tuples)
+        vertices = np.array(list(itertools.chain(*vertices))).reshape(-1, 2)
+        return vertices, list(codes)
+
+
+def get_path_style(path, fill=True):
+    """Get the style dictionary for matplotlib path objects"""
+    style = {}
+    style['alpha'] = path.get_alpha()
+    if style['alpha'] is None:
+        style['alpha'] = 1
+    style['edgecolor'] = color_to_hex(path.get_edgecolor())
+    if fill:
+        style['facecolor'] = color_to_hex(path.get_facecolor())
+    else:
+        style['facecolor'] = 'none'
+    style['edgewidth'] = path.get_linewidth()
+    style['dasharray'] = get_dasharray(path)
+    style['zorder'] = path.get_zorder()
+    return style
+
+
+def get_line_style(line):
+    """Get the style dictionary for matplotlib line objects"""
+    style = {}
+    style['alpha'] = line.get_alpha()
+    if style['alpha'] is None:
+        style['alpha'] = 1
+    style['color'] = color_to_hex(line.get_color())
+    style['linewidth'] = line.get_linewidth()
+    style['dasharray'] = get_dasharray(line)
+    style['zorder'] = line.get_zorder()
+    return style
+
+
+def get_marker_style(line):
+    """Get the style dictionary for matplotlib marker objects"""
+    style = {}
+    style['alpha'] = line.get_alpha()
+    if style['alpha'] is None:
+        style['alpha'] = 1
+
+    style['facecolor'] = color_to_hex(line.get_markerfacecolor())
+    style['edgecolor'] = color_to_hex(line.get_markeredgecolor())
+    style['edgewidth'] = line.get_markeredgewidth()
+
+    style['marker'] = line.get_marker()
+    markerstyle = MarkerStyle(line.get_marker())
+    markersize = line.get_markersize()
+    markertransform = (markerstyle.get_transform()
+                       + Affine2D().scale(markersize, -markersize))
+    style['markerpath'] = SVG_path(markerstyle.get_path(),
+                                   markertransform)
+    style['markersize'] = markersize
+    style['zorder'] = line.get_zorder()
+    return style
+
+
+def get_text_style(text):
+    """Return the text style dict for a text instance"""
+    style = {}
+    style['alpha'] = text.get_alpha()
+    if style['alpha'] is None:
+        style['alpha'] = 1
+    style['fontsize'] = text.get_size()
+    style['color'] = color_to_hex(text.get_color())
+    style['halign'] = text.get_horizontalalignment()  # left, center, right
+    style['valign'] = text.get_verticalalignment()  # baseline, center, top
+    style['malign'] = text._multialignment  # text alignment when '\n' in text
+    style['rotation'] = text.get_rotation()
+    style['zorder'] = text.get_zorder()
+    return style
+
+
+def get_axis_properties(axis):
+    """Return the property dictionary for a matplotlib.Axis instance"""
+    props = {}
+    label1On = axis._major_tick_kw.get('label1On', True)
+
+    if isinstance(axis, matplotlib.axis.XAxis):
+        if label1On:
+            props['position'] = "bottom"
+        else:
+            props['position'] = "top"
+    elif isinstance(axis, matplotlib.axis.YAxis):
+        if label1On:
+            props['position'] = "left"
+        else:
+            props['position'] = "right"
+    else:
+        raise ValueError("{0} should be an Axis instance".format(axis))
+
+    # Use tick values if appropriate
+    locator = axis.get_major_locator()
+    props['nticks'] = len(locator())
+    if isinstance(locator, ticker.FixedLocator):
+        props['tickvalues'] = list(locator())
+    else:
+        props['tickvalues'] = None
+
+    # Find tick formats
+    formatter = axis.get_major_formatter()
+    if isinstance(formatter, ticker.NullFormatter):
+        props['tickformat'] = ""
+    elif isinstance(formatter, ticker.FixedFormatter):
+        props['tickformat'] = list(formatter.seq)
+    elif not any(label.get_visible() for label in axis.get_ticklabels()):
+        props['tickformat'] = ""
+    else:
+        props['tickformat'] = None
+
+    # Get axis scale
+    props['scale'] = axis.get_scale()
+
+    # Get major tick label size (assumes that's all we really care about!)
+    labels = axis.get_ticklabels()
+    if labels:
+        props['fontsize'] = labels[0].get_fontsize()
+    else:
+        props['fontsize'] = None
+
+    # Get associated grid
+    props['grid'] = get_grid_style(axis)
+
+    return props
+
+
+def get_grid_style(axis):
+    gridlines = axis.get_gridlines()
+    if axis._gridOnMajor and len(gridlines) > 0:
+        color = color_to_hex(gridlines[0].get_color())
+        alpha = gridlines[0].get_alpha()
+        dasharray = get_dasharray(gridlines[0])
+        return dict(gridOn=True,
+                    color=color,
+                    dasharray=dasharray,
+                    alpha=alpha)
+    else:
+        return {"gridOn": False}
+
+
+def get_figure_properties(fig):
+    return {'figwidth': fig.get_figwidth(),
+            'figheight': fig.get_figheight(),
+            'dpi': fig.dpi}
+
+
+def get_axes_properties(ax):
+    props = {'axesbg': color_to_hex(ax.patch.get_facecolor()),
+             'axesbgalpha': ax.patch.get_alpha(),
+             'bounds': ax.get_position().bounds,
+             'dynamic': ax.get_navigate(),
+             'axison': ax.axison,
+             'frame_on': ax.get_frame_on(),
+             'axes': [get_axis_properties(ax.xaxis),
+                      get_axis_properties(ax.yaxis)]}
+
+    for axname in ['x', 'y']:
+        axis = getattr(ax, axname + 'axis')
+        domain = getattr(ax, 'get_{0}lim'.format(axname))()
+        lim = domain
+        if isinstance(axis.converter, matplotlib.dates.DateConverter):
+            scale = 'date'
+            try:
+                import pandas as pd
+                from pandas.tseries.converter import PeriodConverter
+            except ImportError:
+                pd = None
+
+            if (pd is not None and isinstance(axis.converter,
+                                              PeriodConverter)):
+                _dates = [pd.Period(ordinal=int(d), freq=axis.freq)
+                          for d in domain]
+                domain = [(d.year, d.month - 1, d.day,
+                           d.hour, d.minute, d.second, 0)
+                          for d in _dates]
+            else:
+                domain = [(d.year, d.month - 1, d.day,
+                           d.hour, d.minute, d.second,
+                           d.microsecond * 1E-3)
+                          for d in matplotlib.dates.num2date(domain)]
+        else:
+            scale = axis.get_scale()
+
+        if scale not in ['date', 'linear', 'log']:
+            raise ValueError("Unknown axis scale: "
+                             "{0}".format(axis[axname].get_scale()))
+
+        props[axname + 'scale'] = scale
+        props[axname + 'lim'] = lim
+        props[axname + 'domain'] = domain
+
+    return props
+
+
+def iter_all_children(obj, skipContainers=False):
+    """
+    Returns an iterator over all childen and nested children using
+    obj's get_children() method
+
+    if skipContainers is true, only childless objects are returned.
+    """
+    if hasattr(obj, 'get_children') and len(obj.get_children()) > 0:
+        for child in obj.get_children():
+            if not skipContainers:
+                yield child
+            # could use `yield from` in python 3...
+            for grandchild in iter_all_children(child, skipContainers):
+                yield grandchild
+    else:
+        yield obj
+
+
+def get_legend_properties(ax, legend):
+    handles, labels = ax.get_legend_handles_labels()
+    visible = legend.get_visible()
+    return {'handles': handles, 'labels': labels, 'visible': visible}
+
+
+def image_to_base64(image):
+    """
+    Convert a matplotlib image to a base64 png representation
+
+    Parameters
+    ----------
+    image : matplotlib image object
+        The image to be converted.
+
+    Returns
+    -------
+    image_base64 : string
+        The UTF8-encoded base64 string representation of the png image.
+    """
+    ax = image.axes
+    binary_buffer = io.BytesIO()
+
+    # image is saved in axes coordinates: we need to temporarily
+    # set the correct limits to get the correct image
+    lim = ax.axis()
+    ax.axis(image.get_extent())
+    image.write_png(binary_buffer)
+    ax.axis(lim)
+
+    binary_buffer.seek(0)
+    return base64.b64encode(binary_buffer.read()).decode('utf-8')
diff --git a/vispy/ext/ordereddict.py b/vispy/ext/ordereddict.py
new file mode 100644
index 0000000..b41b1b6
--- /dev/null
+++ b/vispy/ext/ordereddict.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+from sys import version_info
+
+if version_info[0] > 2 or version_info[1] >= 7:
+    from collections import OrderedDict
+else:
+    from .py24_ordereddict import OrderedDict  # noqa
diff --git a/vispy/ext/png.py b/vispy/ext/png.py
new file mode 100644
index 0000000..91c28c0
--- /dev/null
+++ b/vispy/ext/png.py
@@ -0,0 +1,2215 @@
+#!/usr/bin/env python
+
+# png.py - PNG encoder/decoder in pure Python
+#
+# Copyright (C) 2006 Johann C. Rocholl <johann at browsershots.org>
+# Portions Copyright (C) 2009 David Jones <drj at pobox.com>
+# And probably portions Copyright (C) 2006 Nicko van Someren <nicko at nicko.org>
+#
+# Original concept by Johann C. Rocholl.
+#
+# LICENCE (MIT)
+#
+# 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.
+
+# Adapted for vispy
+
+# http://www.python.org/doc/2.2.3/whatsnew/node5.html
+from __future__ import generators
+
+__version__ = "0.0.17"
+
+from array import array
+import math
+# http://www.python.org/doc/2.4.4/lib/module-operator.html
+import operator
+import struct
+import zlib
+# http://www.python.org/doc/2.4.4/lib/module-warnings.html
+import warnings
+from .six.moves import map as imap
+from .six import string_types
+import itertools
+
+
+__all__ = ['Image', 'Reader', 'Writer', 'write_chunks', 'from_array']
+
+
+# The PNG signature.
+# http://www.w3.org/TR/PNG/#5PNG-file-signature
+_signature = struct.pack('8B', 137, 80, 78, 71, 13, 10, 26, 10)
+
+_adam7 = ((0, 0, 8, 8),
+          (4, 0, 8, 8),
+          (0, 4, 4, 8),
+          (2, 0, 4, 4),
+          (0, 2, 2, 4),
+          (1, 0, 2, 2),
+          (0, 1, 1, 2))
+
+def group(s, n):
+    # See http://www.python.org/doc/2.6/library/functions.html#zip
+    return zip(*[iter(s)]*n)
+
+def isarray(x):
+    """Same as ``isinstance(x, array)`` except on Python 2.2, where it
+    always returns ``False``.  This helps PyPNG work on Python 2.2.
+    """
+
+    try:
+        return isinstance(x, array)
+    except TypeError:
+        # Because on Python 2.2 array.array is not a type.
+        return False
+
+def tostring(row):
+    """ Python3 definition, array.tostring() is deprecated in Python3
+    """
+    return row.tobytes()
+
+# Conditionally convert to bytes.  Works on Python 2 and Python 3.
+try:
+    bytes('', 'ascii')
+    def strtobytes(x): return bytes(x, 'iso8859-1')
+    def bytestostr(x): return str(x, 'iso8859-1')
+except (NameError, TypeError):
+    # We get NameError when bytes() does not exist (most Python
+    # 2.x versions), and TypeError when bytes() exists but is on
+    # Python 2.x (when it is an alias for str() and takes at most
+    # one argument).
+    strtobytes = str
+    bytestostr = str
+
+def interleave_planes(ipixels, apixels, ipsize, apsize):
+    """
+    Interleave (colour) planes, e.g. RGB + A = RGBA.
+
+    Return an array of pixels consisting of the `ipsize` elements of
+    data from each pixel in `ipixels` followed by the `apsize` elements
+    of data from each pixel in `apixels`.  Conventionally `ipixels`
+    and `apixels` are byte arrays so the sizes are bytes, but it
+    actually works with any arrays of the same type.  The returned
+    array is the same type as the input arrays which should be the
+    same type as each other.
+    """
+
+    itotal = len(ipixels)
+    atotal = len(apixels)
+    newtotal = itotal + atotal
+    newpsize = ipsize + apsize
+    # Set up the output buffer
+    # See http://www.python.org/doc/2.4.4/lib/module-array.html#l2h-1356
+    out = array(ipixels.typecode)
+    # It's annoying that there is no cheap way to set the array size :-(
+    out.extend(ipixels)
+    out.extend(apixels)
+    # Interleave in the pixel data
+    for i in range(ipsize):
+        out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize]
+    for i in range(apsize):
+        out[i+ipsize:newtotal:newpsize] = apixels[i:atotal:apsize]
+    return out
+
+def check_palette(palette):
+    """Check a palette argument (to the :class:`Writer` class)
+    for validity.  Returns the palette as a list if okay; raises an
+    exception otherwise.
+    """
+
+    # None is the default and is allowed.
+    if palette is None:
+        return None
+
+    p = list(palette)
+    if not (0 < len(p) <= 256):
+        raise ValueError("a palette must have between 1 and 256 entries")
+    seen_triple = False
+    for i,t in enumerate(p):
+        if len(t) not in (3,4):
+            raise ValueError(
+              "palette entry %d: entries must be 3- or 4-tuples." % i)
+        if len(t) == 3:
+            seen_triple = True
+        if seen_triple and len(t) == 4:
+            raise ValueError(
+              "palette entry %d: all 4-tuples must precede all 3-tuples" % i)
+        for x in t:
+            if int(x) != x or not(0 <= x <= 255):
+                raise ValueError(
+                  "palette entry %d: values must be integer: 0 <= x <= 255" % i)
+    return p
+
+def check_sizes(size, width, height):
+    """Check that these arguments, in supplied, are consistent.
+    Return a (width, height) pair.
+    """
+
+    if not size:
+        return width, height
+
+    if len(size) != 2:
+        raise ValueError(
+          "size argument should be a pair (width, height)")
+    if width is not None and width != size[0]:
+        raise ValueError(
+          "size[0] (%r) and width (%r) should match when both are used."
+            % (size[0], width))
+    if height is not None and height != size[1]:
+        raise ValueError(
+          "size[1] (%r) and height (%r) should match when both are used."
+            % (size[1], height))
+    return size
+
+def check_color(c, greyscale, which):
+    """Checks that a colour argument for transparent or
+    background options is the right form.  Returns the colour
+    (which, if it's a bar integer, is "corrected" to a 1-tuple).
+    """
+
+    if c is None:
+        return c
+    if greyscale:
+        try:
+            l = len(c)
+        except TypeError:
+            c = (c,)
+        if len(c) != 1:
+            raise ValueError("%s for greyscale must be 1-tuple" %
+                which)
+        if not isinteger(c[0]):
+            raise ValueError(
+                "%s colour for greyscale must be integer" % which)
+    else:
+        if not (len(c) == 3 and
+                isinteger(c[0]) and
+                isinteger(c[1]) and
+                isinteger(c[2])):
+            raise ValueError(
+                "%s colour must be a triple of integers" % which)
+    return c
+
+class Error(Exception):
+    def __str__(self):
+        return self.__class__.__name__ + ': ' + ' '.join(self.args)
+
+class FormatError(Error):
+    """Problem with input file format.  In other words, PNG file does
+    not conform to the specification in some way and is invalid.
+    """
+
+class ChunkError(FormatError):
+    pass
+
+
+class Writer:
+    """
+    PNG encoder in pure Python.
+    """
+
+    def __init__(self, width=None, height=None,
+                 size=None,
+                 greyscale=False,
+                 alpha=False,
+                 bitdepth=8,
+                 palette=None,
+                 transparent=None,
+                 background=None,
+                 gamma=None,
+                 compression=None,
+                 interlace=False,
+                 bytes_per_sample=None, # deprecated
+                 planes=None,
+                 colormap=None,
+                 maxval=None,
+                 chunk_limit=2**20):
+        """
+        Create a PNG encoder object.
+
+        Arguments:
+
+        width, height
+          Image size in pixels, as two separate arguments.
+        size
+          Image size (w,h) in pixels, as single argument.
+        greyscale
+          Input data is greyscale, not RGB.
+        alpha
+          Input data has alpha channel (RGBA or LA).
+        bitdepth
+          Bit depth: from 1 to 16.
+        palette
+          Create a palette for a colour mapped image (colour type 3).
+        transparent
+          Specify a transparent colour (create a ``tRNS`` chunk).
+        background
+          Specify a default background colour (create a ``bKGD`` chunk).
+        gamma
+          Specify a gamma value (create a ``gAMA`` chunk).
+        compression
+          zlib compression level: 0 (none) to 9 (more compressed);
+          default: -1 or None.
+        interlace
+          Create an interlaced image.
+        chunk_limit
+          Write multiple ``IDAT`` chunks to save memory.
+
+        The image size (in pixels) can be specified either by using the
+        `width` and `height` arguments, or with the single `size`
+        argument.  If `size` is used it should be a pair (*width*,
+        *height*).
+
+        `greyscale` and `alpha` are booleans that specify whether
+        an image is greyscale (or colour), and whether it has an
+        alpha channel (or not).
+
+        `bitdepth` specifies the bit depth of the source pixel values.
+        Each source pixel value must be an integer between 0 and
+        ``2**bitdepth-1``.  For example, 8-bit images have values
+        between 0 and 255.  PNG only stores images with bit depths of
+        1,2,4,8, or 16.  When `bitdepth` is not one of these values,
+        the next highest valid bit depth is selected, and an ``sBIT``
+        (significant bits) chunk is generated that specifies the
+        original precision of the source image.  In this case the
+        supplied pixel values will be rescaled to fit the range of
+        the selected bit depth.
+
+        The details of which bit depth / colour model combinations the
+        PNG file format supports directly, are somewhat arcane
+        (refer to the PNG specification for full details).  Briefly:
+        "small" bit depths (1,2,4) are only allowed with greyscale and
+        colour mapped images; colour mapped images cannot have bit depth
+        16.
+
+        For colour mapped images (in other words, when the `palette`
+        argument is specified) the `bitdepth` argument must match one of
+        the valid PNG bit depths: 1, 2, 4, or 8.  (It is valid to have a
+        PNG image with a palette and an ``sBIT`` chunk, but the meaning
+        is slightly different; it would be awkward to press the
+        `bitdepth` argument into service for this.)
+
+        The `palette` option, when specified, causes a colour mapped
+        image to be created: the PNG colour type is set to 3; greyscale
+        must not be set; alpha must not be set; transparent must not be
+        set; the bit depth must be 1,2,4, or 8.  When a colour mapped
+        image is created, the pixel values are palette indexes and
+        the `bitdepth` argument specifies the size of these indexes
+        (not the size of the colour values in the palette).
+
+        The palette argument value should be a sequence of 3- or
+        4-tuples.  3-tuples specify RGB palette entries; 4-tuples
+        specify RGBA palette entries.  If both 4-tuples and 3-tuples
+        appear in the sequence then all the 4-tuples must come
+        before all the 3-tuples.  A ``PLTE`` chunk is created; if there
+        are 4-tuples then a ``tRNS`` chunk is created as well.  The
+        ``PLTE`` chunk will contain all the RGB triples in the same
+        sequence; the ``tRNS`` chunk will contain the alpha channel for
+        all the 4-tuples, in the same sequence.  Palette entries
+        are always 8-bit.
+
+        If specified, the `transparent` and `background` parameters must
+        be a tuple with three integer values for red, green, blue, or
+        a simple integer (or singleton tuple) for a greyscale image.
+
+        If specified, the `gamma` parameter must be a positive number
+        (generally, a float).  A ``gAMA`` chunk will be created.
+        Note that this will not change the values of the pixels as
+        they appear in the PNG file, they are assumed to have already
+        been converted appropriately for the gamma specified.
+
+        The `compression` argument specifies the compression level to
+        be used by the ``zlib`` module.  Values from 1 to 9 specify
+        compression, with 9 being "more compressed" (usually smaller
+        and slower, but it doesn't always work out that way).  0 means
+        no compression.  -1 and ``None`` both mean that the default
+        level of compession will be picked by the ``zlib`` module
+        (which is generally acceptable).
+
+        If `interlace` is true then an interlaced image is created
+        (using PNG's so far only interace method, *Adam7*).  This does
+        not affect how the pixels should be presented to the encoder,
+        rather it changes how they are arranged into the PNG file.
+        On slow connexions interlaced images can be partially decoded
+        by the browser to give a rough view of the image that is
+        successively refined as more image data appears.
+
+        .. note ::
+
+          Enabling the `interlace` option requires the entire image
+          to be processed in working memory.
+
+        `chunk_limit` is used to limit the amount of memory used whilst
+        compressing the image.  In order to avoid using large amounts of
+        memory, multiple ``IDAT`` chunks may be created.
+        """
+
+        # At the moment the `planes` argument is ignored;
+        # its purpose is to act as a dummy so that
+        # ``Writer(x, y, **info)`` works, where `info` is a dictionary
+        # returned by Reader.read and friends.
+        # Ditto for `colormap`.
+
+        width, height = check_sizes(size, width, height)
+        del size
+
+        if width <= 0 or height <= 0:
+            raise ValueError("width and height must be greater than zero")
+        if not isinteger(width) or not isinteger(height):
+            raise ValueError("width and height must be integers")
+        # http://www.w3.org/TR/PNG/#7Integers-and-byte-order
+        if width > 2**32-1 or height > 2**32-1:
+            raise ValueError("width and height cannot exceed 2**32-1")
+
+        if alpha and transparent is not None:
+            raise ValueError(
+                "transparent colour not allowed with alpha channel")
+
+        if bytes_per_sample is not None:
+            warnings.warn('please use bitdepth instead of bytes_per_sample',
+                          DeprecationWarning)
+            if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2):
+                raise ValueError(
+                    "bytes per sample must be .125, .25, .5, 1, or 2")
+            bitdepth = int(8*bytes_per_sample)
+        del bytes_per_sample
+        if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth:
+            raise ValueError("bitdepth (%r) must be a positive integer <= 16" %
+              bitdepth)
+
+        self.rescale = None
+        if palette:
+            if bitdepth not in (1,2,4,8):
+                raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8")
+            if transparent is not None:
+                raise ValueError("transparent and palette not compatible")
+            if alpha:
+                raise ValueError("alpha and palette not compatible")
+            if greyscale:
+                raise ValueError("greyscale and palette not compatible")
+        else:
+            # No palette, check for sBIT chunk generation.
+            if alpha or not greyscale:
+                if bitdepth not in (8,16):
+                    targetbitdepth = (8,16)[bitdepth > 8]
+                    self.rescale = (bitdepth, targetbitdepth)
+                    bitdepth = targetbitdepth
+                    del targetbitdepth
+            else:
+                assert greyscale
+                assert not alpha
+                if bitdepth not in (1,2,4,8,16):
+                    if bitdepth > 8:
+                        targetbitdepth = 16
+                    elif bitdepth == 3:
+                        targetbitdepth = 4
+                    else:
+                        assert bitdepth in (5,6,7)
+                        targetbitdepth = 8
+                    self.rescale = (bitdepth, targetbitdepth)
+                    bitdepth = targetbitdepth
+                    del targetbitdepth
+
+        if bitdepth < 8 and (alpha or not greyscale and not palette):
+            raise ValueError(
+              "bitdepth < 8 only permitted with greyscale or palette")
+        if bitdepth > 8 and palette:
+            raise ValueError(
+                "bit depth must be 8 or less for images with palette")
+
+        transparent = check_color(transparent, greyscale, 'transparent')
+        background = check_color(background, greyscale, 'background')
+
+        # It's important that the true boolean values (greyscale, alpha,
+        # colormap, interlace) are converted to bool because Iverson's
+        # convention is relied upon later on.
+        self.width = width
+        self.height = height
+        self.transparent = transparent
+        self.background = background
+        self.gamma = gamma
+        self.greyscale = bool(greyscale)
+        self.alpha = bool(alpha)
+        self.colormap = bool(palette)
+        self.bitdepth = int(bitdepth)
+        self.compression = compression
+        self.chunk_limit = chunk_limit
+        self.interlace = bool(interlace)
+        self.palette = check_palette(palette)
+
+        self.color_type = 4*self.alpha + 2*(not greyscale) + 1*self.colormap
+        assert self.color_type in (0,2,3,4,6)
+
+        self.color_planes = (3,1)[self.greyscale or self.colormap]
+        self.planes = self.color_planes + self.alpha
+        # :todo: fix for bitdepth < 8
+        self.psize = (self.bitdepth/8) * self.planes
+
+    def make_palette(self):
+        """Create the byte sequences for a ``PLTE`` and if necessary a
+        ``tRNS`` chunk.  Returned as a pair (*p*, *t*).  *t* will be
+        ``None`` if no ``tRNS`` chunk is necessary.
+        """
+
+        p = array('B')
+        t = array('B')
+
+        for x in self.palette:
+            p.extend(x[0:3])
+            if len(x) > 3:
+                t.append(x[3])
+        p = tostring(p)
+        t = tostring(t)
+        if t:
+            return p,t
+        return p,None
+
+    def write(self, outfile, rows):
+        """Write a PNG image to the output file.  `rows` should be
+        an iterable that yields each row in boxed row flat pixel
+        format.  The rows should be the rows of the original image,
+        so there should be ``self.height`` rows of ``self.width *
+        self.planes`` values.  If `interlace` is specified (when
+        creating the instance), then an interlaced PNG file will
+        be written.  Supply the rows in the normal image order;
+        the interlacing is carried out internally.
+
+        .. note ::
+
+          Interlacing will require the entire image to be in working
+          memory.
+        """
+
+        if self.interlace:
+            fmt = 'BH'[self.bitdepth > 8]
+            a = array(fmt, itertools.chain(*rows))
+            return self.write_array(outfile, a)
+
+        nrows = self.write_passes(outfile, rows)
+        if nrows != self.height:
+            raise ValueError(
+              "rows supplied (%d) does not match height (%d)" %
+              (nrows, self.height))
+
+    def write_passes(self, outfile, rows, packed=False):
+        """
+        Write a PNG image to the output file.
+
+        Most users are expected to find the :meth:`write` or
+        :meth:`write_array` method more convenient.
+        
+        The rows should be given to this method in the order that
+        they appear in the output file.  For straightlaced images,
+        this is the usual top to bottom ordering, but for interlaced
+        images the rows should have already been interlaced before
+        passing them to this function.
+
+        `rows` should be an iterable that yields each row.  When
+        `packed` is ``False`` the rows should be in boxed row flat pixel
+        format; when `packed` is ``True`` each row should be a packed
+        sequence of bytes.
+        """
+
+        # http://www.w3.org/TR/PNG/#5PNG-file-signature
+        outfile.write(_signature)
+
+        # http://www.w3.org/TR/PNG/#11IHDR
+        write_chunk(outfile, 'IHDR',
+                    struct.pack("!2I5B", self.width, self.height,
+                                self.bitdepth, self.color_type,
+                                0, 0, self.interlace))
+
+        # See :chunk:order
+        # http://www.w3.org/TR/PNG/#11gAMA
+        if self.gamma is not None:
+            write_chunk(outfile, 'gAMA',
+                        struct.pack("!L", int(round(self.gamma*1e5))))
+
+        # See :chunk:order
+        # http://www.w3.org/TR/PNG/#11sBIT
+        if self.rescale:
+            write_chunk(outfile, 'sBIT',
+                struct.pack('%dB' % self.planes,
+                            *[self.rescale[0]]*self.planes))
+        
+        # :chunk:order: Without a palette (PLTE chunk), ordering is
+        # relatively relaxed.  With one, gAMA chunk must precede PLTE
+        # chunk which must precede tRNS and bKGD.
+        # See http://www.w3.org/TR/PNG/#5ChunkOrdering
+        if self.palette:
+            p,t = self.make_palette()
+            write_chunk(outfile, 'PLTE', p)
+            if t:
+                # tRNS chunk is optional. Only needed if palette entries
+                # have alpha.
+                write_chunk(outfile, 'tRNS', t)
+
+        # http://www.w3.org/TR/PNG/#11tRNS
+        if self.transparent is not None:
+            if self.greyscale:
+                write_chunk(outfile, 'tRNS',
+                            struct.pack("!1H", *self.transparent))
+            else:
+                write_chunk(outfile, 'tRNS',
+                            struct.pack("!3H", *self.transparent))
+
+        # http://www.w3.org/TR/PNG/#11bKGD
+        if self.background is not None:
+            if self.greyscale:
+                write_chunk(outfile, 'bKGD',
+                            struct.pack("!1H", *self.background))
+            else:
+                write_chunk(outfile, 'bKGD',
+                            struct.pack("!3H", *self.background))
+
+        # http://www.w3.org/TR/PNG/#11IDAT
+        if self.compression is not None:
+            compressor = zlib.compressobj(self.compression)
+        else:
+            compressor = zlib.compressobj()
+
+        # Choose an extend function based on the bitdepth.  The extend
+        # function packs/decomposes the pixel values into bytes and
+        # stuffs them onto the data array.
+        data = array('B')
+        if self.bitdepth == 8 or packed:
+            extend = data.extend
+        elif self.bitdepth == 16:
+            # Decompose into bytes
+            def extend(sl):
+                fmt = '!%dH' % len(sl)
+                data.extend(array('B', struct.pack(fmt, *sl)))
+        else:
+            # Pack into bytes
+            assert self.bitdepth < 8
+            # samples per byte
+            spb = int(8/self.bitdepth)
+            def extend(sl):
+                a = array('B', sl)
+                # Adding padding bytes so we can group into a whole
+                # number of spb-tuples.
+                l = float(len(a))
+                extra = math.ceil(l / float(spb))*spb - l
+                a.extend([0]*int(extra))
+                # Pack into bytes
+                l = group(a, spb)
+                l = map(lambda e: reduce(lambda x,y:
+                                           (x << self.bitdepth) + y, e), l)
+                data.extend(l)
+        if self.rescale:
+            oldextend = extend
+            factor = \
+              float(2**self.rescale[1]-1) / float(2**self.rescale[0]-1)
+            def extend(sl):
+                oldextend(map(lambda x: int(round(factor*x)), sl))
+
+        # Build the first row, testing mostly to see if we need to
+        # changed the extend function to cope with NumPy integer types
+        # (they cause our ordinary definition of extend to fail, so we
+        # wrap it).  See
+        # http://code.google.com/p/pypng/issues/detail?id=44
+        enumrows = enumerate(rows)
+        del rows
+
+        # First row's filter type.
+        data.append(0)
+        # :todo: Certain exceptions in the call to ``.next()`` or the
+        # following try would indicate no row data supplied.
+        # Should catch.
+        i,row = enumrows.next()
+        try:
+            # If this fails...
+            extend(row)
+        except:
+            # ... try a version that converts the values to int first.
+            # Not only does this work for the (slightly broken) NumPy
+            # types, there are probably lots of other, unknown, "nearly"
+            # int types it works for.
+            def wrapmapint(f):
+                return lambda sl: f(map(int, sl))
+            extend = wrapmapint(extend)
+            del wrapmapint
+            extend(row)
+
+        for i,row in enumrows:
+            # Add "None" filter type.  Currently, it's essential that
+            # this filter type be used for every scanline as we do not
+            # mark the first row of a reduced pass image; that means we
+            # could accidentally compute the wrong filtered scanline if
+            # we used "up", "average", or "paeth" on such a line.
+            data.append(0)
+            extend(row)
+            if len(data) > self.chunk_limit:
+                compressed = compressor.compress(tostring(data))
+                if len(compressed):
+                    write_chunk(outfile, 'IDAT', compressed)
+                # Because of our very witty definition of ``extend``,
+                # above, we must re-use the same ``data`` object.  Hence
+                # we use ``del`` to empty this one, rather than create a
+                # fresh one (which would be my natural FP instinct).
+                del data[:]
+        if len(data):
+            compressed = compressor.compress(tostring(data))
+        else:
+            compressed = strtobytes('')
+        flushed = compressor.flush()
+        if len(compressed) or len(flushed):
+            write_chunk(outfile, 'IDAT', compressed + flushed)
+        # http://www.w3.org/TR/PNG/#11IEND
+        write_chunk(outfile, 'IEND')
+        return i+1
+
+    def write_array(self, outfile, pixels):
+        """
+        Write an array in flat row flat pixel format as a PNG file on
+        the output file.  See also :meth:`write` method.
+        """
+
+        if self.interlace:
+            self.write_passes(outfile, self.array_scanlines_interlace(pixels))
+        else:
+            self.write_passes(outfile, self.array_scanlines(pixels))
+
+    def write_packed(self, outfile, rows):
+        """
+        Write PNG file to `outfile`.  The pixel data comes from `rows`
+        which should be in boxed row packed format.  Each row should be
+        a sequence of packed bytes.
+
+        Technically, this method does work for interlaced images but it
+        is best avoided.  For interlaced images, the rows should be
+        presented in the order that they appear in the file.
+
+        This method should not be used when the source image bit depth
+        is not one naturally supported by PNG; the bit depth should be
+        1, 2, 4, 8, or 16.
+        """
+
+        if self.rescale:
+            raise Error("write_packed method not suitable for bit depth %d" %
+              self.rescale[0])
+        return self.write_passes(outfile, rows, packed=True)
+
+    def convert_pnm(self, infile, outfile):
+        """
+        Convert a PNM file containing raw pixel data into a PNG file
+        with the parameters set in the writer object.  Works for
+        (binary) PGM, PPM, and PAM formats.
+        """
+
+        if self.interlace:
+            pixels = array('B')
+            pixels.fromfile(infile,
+                            (self.bitdepth/8) * self.color_planes *
+                            self.width * self.height)
+            self.write_passes(outfile, self.array_scanlines_interlace(pixels))
+        else:
+            self.write_passes(outfile, self.file_scanlines(infile))
+
+    def convert_ppm_and_pgm(self, ppmfile, pgmfile, outfile):
+        """
+        Convert a PPM and PGM file containing raw pixel data into a
+        PNG outfile with the parameters set in the writer object.
+        """
+        pixels = array('B')
+        pixels.fromfile(ppmfile,
+                        (self.bitdepth/8) * self.color_planes *
+                        self.width * self.height)
+        apixels = array('B')
+        apixels.fromfile(pgmfile,
+                         (self.bitdepth/8) *
+                         self.width * self.height)
+        pixels = interleave_planes(pixels, apixels,
+                                   (self.bitdepth/8) * self.color_planes,
+                                   (self.bitdepth/8))
+        if self.interlace:
+            self.write_passes(outfile, self.array_scanlines_interlace(pixels))
+        else:
+            self.write_passes(outfile, self.array_scanlines(pixels))
+
+    def file_scanlines(self, infile):
+        """
+        Generates boxed rows in flat pixel format, from the input file
+        `infile`.  It assumes that the input file is in a "Netpbm-like"
+        binary format, and is positioned at the beginning of the first
+        pixel.  The number of pixels to read is taken from the image
+        dimensions (`width`, `height`, `planes`) and the number of bytes
+        per value is implied by the image `bitdepth`.
+        """
+
+        # Values per row
+        vpr = self.width * self.planes
+        row_bytes = vpr
+        if self.bitdepth > 8:
+            assert self.bitdepth == 16
+            row_bytes *= 2
+            fmt = '>%dH' % vpr
+            def line():
+                return array('H', struct.unpack(fmt, infile.read(row_bytes)))
+        else:
+            def line():
+                scanline = array('B', infile.read(row_bytes))
+                return scanline
+        for y in range(self.height):
+            yield line()
+
+    def array_scanlines(self, pixels):
+        """
+        Generates boxed rows (flat pixels) from flat rows (flat pixels)
+        in an array.
+        """
+
+        # Values per row
+        vpr = self.width * self.planes
+        stop = 0
+        for y in range(self.height):
+            start = stop
+            stop = start + vpr
+            yield pixels[start:stop]
+
+    def array_scanlines_interlace(self, pixels):
+        """
+        Generator for interlaced scanlines from an array.  `pixels` is
+        the full source image in flat row flat pixel format.  The
+        generator yields each scanline of the reduced passes in turn, in
+        boxed row flat pixel format.
+        """
+
+        # http://www.w3.org/TR/PNG/#8InterlaceMethods
+        # Array type.
+        fmt = 'BH'[self.bitdepth > 8]
+        # Value per row
+        vpr = self.width * self.planes
+        for xstart, ystart, xstep, ystep in _adam7:
+            if xstart >= self.width:
+                continue
+            # Pixels per row (of reduced image)
+            ppr = int(math.ceil((self.width-xstart)/float(xstep)))
+            # number of values in reduced image row.
+            row_len = ppr*self.planes
+            for y in range(ystart, self.height, ystep):
+                if xstep == 1:
+                    offset = y * vpr
+                    yield pixels[offset:offset+vpr]
+                else:
+                    row = array(fmt)
+                    # There's no easier way to set the length of an array
+                    row.extend(pixels[0:row_len])
+                    offset = y * vpr + xstart * self.planes
+                    end_offset = (y+1) * vpr
+                    skip = self.planes * xstep
+                    for i in range(self.planes):
+                        row[i::self.planes] = \
+                            pixels[offset+i:end_offset:skip]
+                    yield row
+
+def write_chunk(outfile, tag, data=strtobytes('')):
+    """
+    Write a PNG chunk to the output file, including length and
+    checksum.
+    """
+
+    # http://www.w3.org/TR/PNG/#5Chunk-layout
+    outfile.write(struct.pack("!I", len(data)))
+    tag = strtobytes(tag)
+    outfile.write(tag)
+    outfile.write(data)
+    checksum = zlib.crc32(tag)
+    checksum = zlib.crc32(data, checksum)
+    checksum &= 2**32-1
+    outfile.write(struct.pack("!I", checksum))
+
+def write_chunks(out, chunks):
+    """Create a PNG file by writing out the chunks."""
+
+    out.write(_signature)
+    for chunk in chunks:
+        write_chunk(out, *chunk)
+
+def filter_scanline(type, line, fo, prev=None):
+    """Apply a scanline filter to a scanline.  `type` specifies the
+    filter type (0 to 4); `line` specifies the current (unfiltered)
+    scanline as a sequence of bytes; `prev` specifies the previous
+    (unfiltered) scanline as a sequence of bytes. `fo` specifies the
+    filter offset; normally this is size of a pixel in bytes (the number
+    of bytes per sample times the number of channels), but when this is
+    < 1 (for bit depths < 8) then the filter offset is 1.
+    """
+
+    assert 0 <= type < 5
+
+    # The output array.  Which, pathetically, we extend one-byte at a
+    # time (fortunately this is linear).
+    out = array('B', [type])
+
+    def sub():
+        ai = -fo
+        for x in line:
+            if ai >= 0:
+                x = (x - line[ai]) & 0xff
+            out.append(x)
+            ai += 1
+    def up():
+        for i,x in enumerate(line):
+            x = (x - prev[i]) & 0xff
+            out.append(x)
+    def average():
+        ai = -fo
+        for i,x in enumerate(line):
+            if ai >= 0:
+                x = (x - ((line[ai] + prev[i]) >> 1)) & 0xff
+            else:
+                x = (x - (prev[i] >> 1)) & 0xff
+            out.append(x)
+            ai += 1
+    def paeth():
+        # http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
+        ai = -fo # also used for ci
+        for i,x in enumerate(line):
+            a = 0
+            b = prev[i]
+            c = 0
+
+            if ai >= 0:
+                a = line[ai]
+                c = prev[ai]
+            p = a + b - c
+            pa = abs(p - a)
+            pb = abs(p - b)
+            pc = abs(p - c)
+            if pa <= pb and pa <= pc:
+                Pr = a
+            elif pb <= pc:
+                Pr = b
+            else:
+                Pr = c
+
+            x = (x - Pr) & 0xff
+            out.append(x)
+            ai += 1
+
+    if not prev:
+        # We're on the first line.  Some of the filters can be reduced
+        # to simpler cases which makes handling the line "off the top"
+        # of the image simpler.  "up" becomes "none"; "paeth" becomes
+        # "left" (non-trivial, but true). "average" needs to be handled
+        # specially.
+        if type == 2: # "up"
+            type = 0
+        elif type == 3:
+            prev = [0]*len(line)
+        elif type == 4: # "paeth"
+            type = 1
+    if type == 0:
+        out.extend(line)
+    elif type == 1:
+        sub()
+    elif type == 2:
+        up()
+    elif type == 3:
+        average()
+    else: # type == 4
+        paeth()
+    return out
+
+
+def from_array(a, mode=None, info={}):
+    """Create a PNG :class:`Image` object from a 2- or 3-dimensional
+    array.  One application of this function is easy PIL-style saving:
+    ``png.from_array(pixels, 'L').save('foo.png')``.
+
+    .. note :
+
+      The use of the term *3-dimensional* is for marketing purposes
+      only.  It doesn't actually work.  Please bear with us.  Meanwhile
+      enjoy the complimentary snacks (on request) and please use a
+      2-dimensional array.
+    
+    Unless they are specified using the *info* parameter, the PNG's
+    height and width are taken from the array size.  For a 3 dimensional
+    array the first axis is the height; the second axis is the width;
+    and the third axis is the channel number.  Thus an RGB image that is
+    16 pixels high and 8 wide will use an array that is 16x8x3.  For 2
+    dimensional arrays the first axis is the height, but the second axis
+    is ``width*channels``, so an RGB image that is 16 pixels high and 8
+    wide will use a 2-dimensional array that is 16x24 (each row will be
+    8*3==24 sample values).
+
+    *mode* is a string that specifies the image colour format in a
+    PIL-style mode.  It can be:
+
+    ``'L'``
+      greyscale (1 channel)
+    ``'LA'``
+      greyscale with alpha (2 channel)
+    ``'RGB'``
+      colour image (3 channel)
+    ``'RGBA'``
+      colour image with alpha (4 channel)
+
+    The mode string can also specify the bit depth (overriding how this
+    function normally derives the bit depth, see below).  Appending
+    ``';16'`` to the mode will cause the PNG to be 16 bits per channel;
+    any decimal from 1 to 16 can be used to specify the bit depth.
+
+    When a 2-dimensional array is used *mode* determines how many
+    channels the image has, and so allows the width to be derived from
+    the second array dimension.
+
+    The array is expected to be a ``numpy`` array, but it can be any
+    suitable Python sequence.  For example, a list of lists can be used:
+    ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``.  The exact
+    rules are: ``len(a)`` gives the first dimension, height;
+    ``len(a[0])`` gives the second dimension; ``len(a[0][0])`` gives the
+    third dimension, unless an exception is raised in which case a
+    2-dimensional array is assumed.  It's slightly more complicated than
+    that because an iterator of rows can be used, and it all still
+    works.  Using an iterator allows data to be streamed efficiently.
+
+    The bit depth of the PNG is normally taken from the array element's
+    datatype (but if *mode* specifies a bitdepth then that is used
+    instead).  The array element's datatype is determined in a way which
+    is supposed to work both for ``numpy`` arrays and for Python
+    ``array.array`` objects.  A 1 byte datatype will give a bit depth of
+    8, a 2 byte datatype will give a bit depth of 16.  If the datatype
+    does not have an implicit size, for example it is a plain Python
+    list of lists, as above, then a default of 8 is used.
+
+    The *info* parameter is a dictionary that can be used to specify
+    metadata (in the same style as the arguments to the
+    :class:``png.Writer`` class).  For this function the keys that are
+    useful are:
+    
+    height
+      overrides the height derived from the array dimensions and allows
+      *a* to be an iterable.
+    width
+      overrides the width derived from the array dimensions.
+    bitdepth
+      overrides the bit depth derived from the element datatype (but
+      must match *mode* if that also specifies a bit depth).
+
+    Generally anything specified in the
+    *info* dictionary will override any implicit choices that this
+    function would otherwise make, but must match any explicit ones.
+    For example, if the *info* dictionary has a ``greyscale`` key then
+    this must be true when mode is ``'L'`` or ``'LA'`` and false when
+    mode is ``'RGB'`` or ``'RGBA'``.
+    """
+
+    # We abuse the *info* parameter by modifying it.  Take a copy here.
+    # (Also typechecks *info* to some extent).
+    info = dict(info)
+
+    # Syntax check mode string.
+    bitdepth = None
+    try:
+        # Assign the 'L' or 'RGBA' part to `gotmode`.
+        if mode.startswith('L'):
+            gotmode = 'L'
+            mode = mode[1:]
+        elif mode.startswith('RGB'):
+            gotmode = 'RGB'
+            mode = mode[3:]
+        else:
+            raise Error()
+        if mode.startswith('A'):
+            gotmode += 'A'
+            mode = mode[1:]
+
+        # Skip any optional ';'
+        while mode.startswith(';'):
+            mode = mode[1:]
+
+        # Parse optional bitdepth
+        if mode:
+            try:
+                bitdepth = int(mode)
+            except (TypeError, ValueError):
+                raise Error()
+    except Error:
+        raise Error("mode string should be 'RGB' or 'L;16' or similar.")
+    mode = gotmode
+
+    # Get bitdepth from *mode* if possible.
+    if bitdepth:
+        if info.get('bitdepth') and bitdepth != info['bitdepth']:
+            raise Error("mode bitdepth (%d) should match info bitdepth (%d)." %
+              (bitdepth, info['bitdepth']))
+        info['bitdepth'] = bitdepth
+
+    # Fill in and/or check entries in *info*.
+    # Dimensions.
+    if 'size' in info:
+        # Check width, height, size all match where used.
+        for dimension,axis in [('width', 0), ('height', 1)]:
+            if dimension in info:
+                if info[dimension] != info['size'][axis]:
+                    raise Error(
+                      "info[%r] should match info['size'][%r]." %
+                      (dimension, axis))
+        info['width'],info['height'] = info['size']
+    if 'height' not in info:
+        try:
+            l = len(a)
+        except TypeError:
+            raise Error(
+              "len(a) does not work, supply info['height'] instead.")
+        info['height'] = l
+    # Colour format.
+    if 'greyscale' in info:
+        if bool(info['greyscale']) != ('L' in mode):
+            raise Error("info['greyscale'] should match mode.")
+    info['greyscale'] = 'L' in mode
+    if 'alpha' in info:
+        if bool(info['alpha']) != ('A' in mode):
+            raise Error("info['alpha'] should match mode.")
+    info['alpha'] = 'A' in mode
+
+    planes = len(mode)
+    if 'planes' in info:
+        if info['planes'] != planes:
+            raise Error("info['planes'] should match mode.")
+
+    # In order to work out whether we the array is 2D or 3D we need its
+    # first row, which requires that we take a copy of its iterator.
+    # We may also need the first row to derive width and bitdepth.
+    a,t = itertools.tee(a)
+    row = t.next()
+    del t
+    try:
+        row[0][0]
+        threed = True
+        testelement = row[0]
+    except (IndexError, TypeError):
+        threed = False
+        testelement = row
+    if 'width' not in info:
+        if threed:
+            width = len(row)
+        else:
+            width = len(row) // planes
+        info['width'] = width
+
+    # Not implemented yet
+    assert not threed
+
+    if 'bitdepth' not in info:
+        try:
+            dtype = testelement.dtype
+            # goto the "else:" clause.  Sorry.
+        except AttributeError:
+            try:
+                # Try a Python array.array.
+                bitdepth = 8 * testelement.itemsize
+            except AttributeError:
+                # We can't determine it from the array element's
+                # datatype, use a default of 8.
+                bitdepth = 8
+        else:
+            # If we got here without exception, we now assume that
+            # the array is a numpy array.
+            if dtype.kind == 'b':
+                bitdepth = 1
+            else:
+                bitdepth = 8 * dtype.itemsize
+        info['bitdepth'] = bitdepth
+
+    for thing in 'width height bitdepth greyscale alpha'.split():
+        assert thing in info
+    return Image(a, info)
+
+# So that refugee's from PIL feel more at home.  Not documented.
+fromarray = from_array
+
+class Image:
+    """A PNG image.  You can create an :class:`Image` object from
+    an array of pixels by calling :meth:`png.from_array`.  It can be
+    saved to disk with the :meth:`save` method.
+    """
+
+    def __init__(self, rows, info):
+        """
+        .. note ::
+        
+          The constructor is not public.  Please do not call it.
+        """
+        
+        self.rows = rows
+        self.info = info
+
+    def save(self, file):
+        """Save the image to *file*.  If *file* looks like an open file
+        descriptor then it is used, otherwise it is treated as a
+        filename and a fresh file is opened.
+
+        In general, you can only call this method once; after it has
+        been called the first time and the PNG image has been saved, the
+        source data will have been streamed, and cannot be streamed
+        again.
+        """
+
+        w = Writer(**self.info)
+
+        try:
+            file.write
+            def close(): pass
+        except AttributeError:
+            file = open(file, 'wb')
+            def close(): file.close()
+
+        try:
+            w.write(file, self.rows)
+        finally:
+            close()
+
+class _readable:
+    """
+    A simple file-like interface for strings and arrays.
+    """
+
+    def __init__(self, buf):
+        self.buf = buf
+        self.offset = 0
+
+    def read(self, n):
+        r = self.buf[self.offset:self.offset+n]
+        if isarray(r):
+            r = r.tostring()
+        self.offset += n
+        return r
+
+
+class Reader:
+    """
+    PNG decoder in pure Python.
+    """
+
+    def __init__(self, _guess=None, **kw):
+        """
+        Create a PNG decoder object.
+
+        The constructor expects exactly one keyword argument. If you
+        supply a positional argument instead, it will guess the input
+        type. You can choose among the following keyword arguments:
+
+        filename
+          Name of input file (a PNG file).
+        file
+          A file-like object (object with a read() method).
+        bytes
+          ``array`` or ``string`` with PNG data.
+
+        """
+        if ((_guess is not None and len(kw) != 0) or
+            (_guess is None and len(kw) != 1)):
+            raise TypeError("Reader() takes exactly 1 argument")
+
+        # Will be the first 8 bytes, later on.  See validate_signature.
+        self.signature = None
+        self.transparent = None
+        # A pair of (len,type) if a chunk has been read but its data and
+        # checksum have not (in other words the file position is just
+        # past the 4 bytes that specify the chunk type).  See preamble
+        # method for how this is used.
+        self.atchunk = None
+
+        if _guess is not None:
+            if isarray(_guess):
+                kw["bytes"] = _guess
+            elif isinstance(_guess, string_types):
+                kw["filename"] = _guess
+            elif hasattr(_guess, 'read'):
+                kw["file"] = _guess
+
+        if "filename" in kw:
+            self.file = open(kw["filename"], "rb")
+        elif "file" in kw:
+            self.file = kw["file"]
+        elif "bytes" in kw:
+            self.file = _readable(kw["bytes"])
+        else:
+            raise TypeError("expecting filename, file or bytes array")
+
+
+    def chunk(self, seek=None, lenient=False):
+        """
+        Read the next PNG chunk from the input file; returns a
+        (*type*,*data*) tuple.  *type* is the chunk's type as a string
+        (all PNG chunk types are 4 characters long).  *data* is the
+        chunk's data content, as a string.
+
+        If the optional `seek` argument is
+        specified then it will keep reading chunks until it either runs
+        out of file or finds the type specified by the argument.  Note
+        that in general the order of chunks in PNGs is unspecified, so
+        using `seek` can cause you to miss chunks.
+
+        If the optional `lenient` argument evaluates to True,
+        checksum failures will raise warnings rather than exceptions.
+        """
+
+        self.validate_signature()
+
+        while True:
+            # http://www.w3.org/TR/PNG/#5Chunk-layout
+            if not self.atchunk:
+                self.atchunk = self.chunklentype()
+            length,type = self.atchunk
+            self.atchunk = None
+            data = self.file.read(length)
+            if len(data) != length:
+                raise ChunkError('Chunk %s too short for required %i octets.'
+                  % (type, length))
+            checksum = self.file.read(4)
+            if len(checksum) != 4:
+                raise ValueError('Chunk %s too short for checksum.', tag)
+            if seek and type != seek:
+                continue
+            verify = zlib.crc32(strtobytes(type))
+            verify = zlib.crc32(data, verify)
+            # Whether the output from zlib.crc32 is signed or not varies
+            # according to hideous implementation details, see
+            # http://bugs.python.org/issue1202 .
+            # We coerce it to be positive here (in a way which works on
+            # Python 2.3 and older).
+            verify &= 2**32 - 1
+            verify = struct.pack('!I', verify)
+            if checksum != verify:
+                (a, ) = struct.unpack('!I', checksum)
+                (b, ) = struct.unpack('!I', verify)
+                message = "Checksum error in %s chunk: 0x%08X != 0x%08X." % (type, a, b)
+                if lenient:
+                    warnings.warn(message, RuntimeWarning)
+                else:
+                    raise ChunkError(message)
+            return type, data
+
+    def chunks(self):
+        """Return an iterator that will yield each chunk as a
+        (*chunktype*, *content*) pair.
+        """
+
+        while True:
+            t,v = self.chunk()
+            yield t,v
+            if t == 'IEND':
+                break
+
+    def undo_filter(self, filter_type, scanline, previous):
+        """Undo the filter for a scanline.  `scanline` is a sequence of
+        bytes that does not include the initial filter type byte.
+        `previous` is decoded previous scanline (for straightlaced
+        images this is the previous pixel row, but for interlaced
+        images, it is the previous scanline in the reduced image, which
+        in general is not the previous pixel row in the final image).
+        When there is no previous scanline (the first row of a
+        straightlaced image, or the first row in one of the passes in an
+        interlaced image), then this argument should be ``None``.
+
+        The scanline will have the effects of filtering removed, and the
+        result will be returned as a fresh sequence of bytes.
+        """
+
+        # :todo: Would it be better to update scanline in place?
+        # Yes, with the Cython extension making the undo_filter fast,
+        # updating scanline inplace makes the code 3 times faster
+        # (reading 50 images of 800x800 went from 40s to 16s)
+        result = scanline
+
+        if filter_type == 0:
+            return result
+
+        if filter_type not in (1,2,3,4):
+            raise FormatError('Invalid PNG Filter Type.'
+              '  See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .')
+
+        # Filter unit.  The stride from one pixel to the corresponding
+        # byte from the previous pixel.  Normally this is the pixel
+        # size in bytes, but when this is smaller than 1, the previous
+        # byte is used instead.
+        fu = max(1, self.psize)
+
+        # For the first line of a pass, synthesize a dummy previous
+        # line.  An alternative approach would be to observe that on the
+        # first line 'up' is the same as 'null', 'paeth' is the same
+        # as 'sub', with only 'average' requiring any special case.
+        if not previous:
+            previous = array('B', [0]*len(scanline))
+
+        def sub():
+            """Undo sub filter."""
+
+            ai = 0
+            # Loop starts at index fu.  Observe that the initial part
+            # of the result is already filled in correctly with
+            # scanline.
+            for i in range(fu, len(result)):
+                x = scanline[i]
+                a = result[ai]
+                result[i] = (x + a) & 0xff
+                ai += 1
+
+        def up():
+            """Undo up filter."""
+
+            for i in range(len(result)):
+                x = scanline[i]
+                b = previous[i]
+                result[i] = (x + b) & 0xff
+
+        def average():
+            """Undo average filter."""
+
+            ai = -fu
+            for i in range(len(result)):
+                x = scanline[i]
+                if ai < 0:
+                    a = 0
+                else:
+                    a = result[ai]
+                b = previous[i]
+                result[i] = (x + ((a + b) >> 1)) & 0xff
+                ai += 1
+
+        def paeth():
+            """Undo Paeth filter."""
+
+            # Also used for ci.
+            ai = -fu
+            for i in range(len(result)):
+                x = scanline[i]
+                if ai < 0:
+                    a = c = 0
+                else:
+                    a = result[ai]
+                    c = previous[ai]
+                b = previous[i]
+                p = a + b - c
+                pa = abs(p - a)
+                pb = abs(p - b)
+                pc = abs(p - c)
+                if pa <= pb and pa <= pc:
+                    pr = a
+                elif pb <= pc:
+                    pr = b
+                else:
+                    pr = c
+                result[i] = (x + pr) & 0xff
+                ai += 1
+
+        # Call appropriate filter algorithm.  Note that 0 has already
+        # been dealt with.
+        (None,
+         pngfilters.undo_filter_sub,
+         pngfilters.undo_filter_up,
+         pngfilters.undo_filter_average,
+         pngfilters.undo_filter_paeth)[filter_type](fu, scanline, previous, result)
+        return result
+
+    def deinterlace(self, raw):
+        """
+        Read raw pixel data, undo filters, deinterlace, and flatten.
+        Return in flat row flat pixel format.
+        """
+
+        # Values per row (of the target image)
+        vpr = self.width * self.planes
+
+        # Make a result array, and make it big enough.  Interleaving
+        # writes to the output array randomly (well, not quite), so the
+        # entire output array must be in memory.
+        fmt = 'BH'[self.bitdepth > 8]
+        a = array(fmt, [0]*vpr*self.height)
+        source_offset = 0
+
+        for xstart, ystart, xstep, ystep in _adam7:
+            if xstart >= self.width:
+                continue
+            # The previous (reconstructed) scanline.  None at the
+            # beginning of a pass to indicate that there is no previous
+            # line.
+            recon = None
+            # Pixels per row (reduced pass image)
+            ppr = int(math.ceil((self.width-xstart)/float(xstep)))
+            # Row size in bytes for this pass.
+            row_size = int(math.ceil(self.psize * ppr))
+            for y in range(ystart, self.height, ystep):
+                filter_type = raw[source_offset]
+                source_offset += 1
+                scanline = raw[source_offset:source_offset+row_size]
+                source_offset += row_size
+                recon = self.undo_filter(filter_type, scanline, recon)
+                # Convert so that there is one element per pixel value
+                flat = self.serialtoflat(recon, ppr)
+                if xstep == 1:
+                    assert xstart == 0
+                    offset = y * vpr
+                    a[offset:offset+vpr] = flat
+                else:
+                    offset = y * vpr + xstart * self.planes
+                    end_offset = (y+1) * vpr
+                    skip = self.planes * xstep
+                    for i in range(self.planes):
+                        a[offset+i:end_offset:skip] = \
+                            flat[i::self.planes]
+        return a
+
+    def iterboxed(self, rows):
+        """Iterator that yields each scanline in boxed row flat pixel
+        format.  `rows` should be an iterator that yields the bytes of
+        each row in turn.
+        """
+
+        def asvalues(raw):
+            """Convert a row of raw bytes into a flat row.  Result will
+            be a freshly allocated object, not shared with
+            argument.
+            """
+
+            if self.bitdepth == 8:
+                return array('B', raw)
+            if self.bitdepth == 16:
+                raw = tostring(raw)
+                return array('H', struct.unpack('!%dH' % (len(raw)//2), raw))
+            assert self.bitdepth < 8
+            width = self.width
+            # Samples per byte
+            spb = 8//self.bitdepth
+            out = array('B')
+            mask = 2**self.bitdepth - 1
+            shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
+            for o in raw:
+                out.extend(map(lambda i: mask&(o>>i), shifts))
+            return out[:width]
+
+        return imap(asvalues, rows)
+
+    def serialtoflat(self, bytes, width=None):
+        """Convert serial format (byte stream) pixel data to flat row
+        flat pixel.
+        """
+
+        if self.bitdepth == 8:
+            return bytes
+        if self.bitdepth == 16:
+            bytes = tostring(bytes)
+            return array('H',
+              struct.unpack('!%dH' % (len(bytes)//2), bytes))
+        assert self.bitdepth < 8
+        if width is None:
+            width = self.width
+        # Samples per byte
+        spb = 8//self.bitdepth
+        out = array('B')
+        mask = 2**self.bitdepth - 1
+        shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
+        l = width
+        for o in bytes:
+            out.extend([(mask&(o>>s)) for s in shifts][:l])
+            l -= spb
+            if l <= 0:
+                l = width
+        return out
+
+    def iterstraight(self, raw):
+        """Iterator that undoes the effect of filtering, and yields
+        each row in serialised format (as a sequence of bytes).
+        Assumes input is straightlaced.  `raw` should be an iterable
+        that yields the raw bytes in chunks of arbitrary size.
+        """
+
+        # length of row, in bytes
+        rb = self.row_bytes
+        a = array('B')
+        # The previous (reconstructed) scanline.  None indicates first
+        # line of image.
+        recon = None
+        for some in raw:
+            a.extend(some)
+            while len(a) >= rb + 1:
+                filter_type = a[0]
+                scanline = a[1:rb+1]
+                del a[:rb+1]
+                recon = self.undo_filter(filter_type, scanline, recon)
+                yield recon
+        if len(a) != 0:
+            # :file:format We get here with a file format error:
+            # when the available bytes (after decompressing) do not
+            # pack into exact rows.
+            raise FormatError(
+              'Wrong size for decompressed IDAT chunk.')
+        assert len(a) == 0
+
+    def validate_signature(self):
+        """If signature (header) has not been read then read and
+        validate it; otherwise do nothing.
+        """
+
+        if self.signature:
+            return
+        self.signature = self.file.read(8)
+        if self.signature != _signature:
+            raise FormatError("PNG file has invalid signature.")
+
+    def preamble(self, lenient=False):
+        """
+        Extract the image metadata by reading the initial part of
+        the PNG file up to the start of the ``IDAT`` chunk.  All the
+        chunks that precede the ``IDAT`` chunk are read and either
+        processed for metadata or discarded.
+
+        If the optional `lenient` argument evaluates to True, checksum
+        failures will raise warnings rather than exceptions.
+        """
+
+        self.validate_signature()
+
+        while True:
+            if not self.atchunk:
+                self.atchunk = self.chunklentype()
+                if self.atchunk is None:
+                    raise FormatError(
+                      'This PNG file has no IDAT chunks.')
+            if self.atchunk[1] == 'IDAT':
+                return
+            self.process_chunk(lenient=lenient)
+
+    def chunklentype(self):
+        """Reads just enough of the input to determine the next
+        chunk's length and type, returned as a (*length*, *type*) pair
+        where *type* is a string.  If there are no more chunks, ``None``
+        is returned.
+        """
+
+        x = self.file.read(8)
+        if not x:
+            return None
+        if len(x) != 8:
+            raise FormatError(
+              'End of file whilst reading chunk length and type.')
+        length,type = struct.unpack('!I4s', x)
+        type = bytestostr(type)
+        if length > 2**31-1:
+            raise FormatError('Chunk %s is too large: %d.' % (type,length))
+        return length,type
+
+    def process_chunk(self, lenient=False):
+        """Process the next chunk and its data.  This only processes the
+        following chunk types, all others are ignored: ``IHDR``,
+        ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``.
+
+        If the optional `lenient` argument evaluates to True,
+        checksum failures will raise warnings rather than exceptions.
+        """
+
+        type, data = self.chunk(lenient=lenient)
+        method = '_process_' + type
+        m = getattr(self, method, None)
+        if m:
+            m(data)
+
+    def _process_IHDR(self, data):
+        # http://www.w3.org/TR/PNG/#11IHDR
+        if len(data) != 13:
+            raise FormatError('IHDR chunk has incorrect length.')
+        (self.width, self.height, self.bitdepth, self.color_type,
+         self.compression, self.filter,
+         self.interlace) = struct.unpack("!2I5B", data)
+
+        check_bitdepth_colortype(self.bitdepth, self.color_type)
+
+        if self.compression != 0:
+            raise Error("unknown compression method %d" % self.compression)
+        if self.filter != 0:
+            raise FormatError("Unknown filter method %d,"
+              " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ."
+              % self.filter)
+        if self.interlace not in (0,1):
+            raise FormatError("Unknown interlace method %d,"
+              " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ."
+              % self.interlace)
+
+        # Derived values
+        # http://www.w3.org/TR/PNG/#6Colour-values
+        colormap =  bool(self.color_type & 1)
+        greyscale = not (self.color_type & 2)
+        alpha = bool(self.color_type & 4)
+        color_planes = (3,1)[greyscale or colormap]
+        planes = color_planes + alpha
+
+        self.colormap = colormap
+        self.greyscale = greyscale
+        self.alpha = alpha
+        self.color_planes = color_planes
+        self.planes = planes
+        self.psize = float(self.bitdepth)/float(8) * planes
+        if int(self.psize) == self.psize:
+            self.psize = int(self.psize)
+        self.row_bytes = int(math.ceil(self.width * self.psize))
+        # Stores PLTE chunk if present, and is used to check
+        # chunk ordering constraints.
+        self.plte = None
+        # Stores tRNS chunk if present, and is used to check chunk
+        # ordering constraints.
+        self.trns = None
+        # Stores sbit chunk if present.
+        self.sbit = None
+
+    def _process_PLTE(self, data):
+        # http://www.w3.org/TR/PNG/#11PLTE
+        if self.plte:
+            warnings.warn("Multiple PLTE chunks present.")
+        self.plte = data
+        if len(data) % 3 != 0:
+            raise FormatError(
+              "PLTE chunk's length should be a multiple of 3.")
+        if len(data) > (2**self.bitdepth)*3:
+            raise FormatError("PLTE chunk is too long.")
+        if len(data) == 0:
+            raise FormatError("Empty PLTE is not allowed.")
+
+    def _process_bKGD(self, data):
+        try:
+            if self.colormap:
+                if not self.plte:
+                    warnings.warn(
+                      "PLTE chunk is required before bKGD chunk.")
+                self.background = struct.unpack('B', data)
+            else:
+                self.background = struct.unpack("!%dH" % self.color_planes,
+                  data)
+        except struct.error:
+            raise FormatError("bKGD chunk has incorrect length.")
+
+    def _process_tRNS(self, data):
+        # http://www.w3.org/TR/PNG/#11tRNS
+        self.trns = data
+        if self.colormap:
+            if not self.plte:
+                warnings.warn("PLTE chunk is required before tRNS chunk.")
+            else:
+                if len(data) > len(self.plte)/3:
+                    # Was warning, but promoted to Error as it
+                    # would otherwise cause pain later on.
+                    raise FormatError("tRNS chunk is too long.")
+        else:
+            if self.alpha:
+                raise FormatError(
+                  "tRNS chunk is not valid with colour type %d." %
+                  self.color_type)
+            try:
+                self.transparent = \
+                    struct.unpack("!%dH" % self.color_planes, data)
+            except struct.error:
+                raise FormatError("tRNS chunk has incorrect length.")
+
+    def _process_gAMA(self, data):
+        try:
+            self.gamma = struct.unpack("!L", data)[0] / 100000.0
+        except struct.error:
+            raise FormatError("gAMA chunk has incorrect length.")
+
+    def _process_sBIT(self, data):
+        self.sbit = data
+        if (self.colormap and len(data) != 3 or
+            not self.colormap and len(data) != self.planes):
+            raise FormatError("sBIT chunk has incorrect length.")
+
+    def read(self, lenient=False):
+        """
+        Read the PNG file and decode it.  Returns (`width`, `height`,
+        `pixels`, `metadata`).
+
+        May use excessive memory.
+
+        `pixels` are returned in boxed row flat pixel format.
+
+        If the optional `lenient` argument evaluates to True,
+        checksum failures will raise warnings rather than exceptions.
+        """
+
+        def iteridat():
+            """Iterator that yields all the ``IDAT`` chunks as strings."""
+            while True:
+                try:
+                    type, data = self.chunk(lenient=lenient)
+                except ValueError as e:
+                    raise ChunkError(e.args[0])
+                if type == 'IEND':
+                    # http://www.w3.org/TR/PNG/#11IEND
+                    break
+                if type != 'IDAT':
+                    continue
+                # type == 'IDAT'
+                # http://www.w3.org/TR/PNG/#11IDAT
+                if self.colormap and not self.plte:
+                    warnings.warn("PLTE chunk is required before IDAT chunk")
+                yield data
+
+        def iterdecomp(idat):
+            """Iterator that yields decompressed strings.  `idat` should
+            be an iterator that yields the ``IDAT`` chunk data.
+            """
+
+            # Currently, with no max_length parameter to decompress,
+            # this routine will do one yield per IDAT chunk: Not very
+            # incremental.
+            d = zlib.decompressobj()
+            # Each IDAT chunk is passed to the decompressor, then any
+            # remaining state is decompressed out.
+            for data in idat:
+                # :todo: add a max_length argument here to limit output
+                # size.
+                yield array('B', d.decompress(data))
+            yield array('B', d.flush())
+
+        self.preamble(lenient=lenient)
+        raw = iterdecomp(iteridat())
+
+        if self.interlace:
+            raw = array('B', itertools.chain(*raw))
+            arraycode = 'BH'[self.bitdepth>8]
+            # Like :meth:`group` but producing an array.array object for
+            # each row.
+            pixels = imap(lambda *row: array(arraycode, row),
+                       *[iter(self.deinterlace(raw))]*self.width*self.planes)
+        else:
+            pixels = self.iterboxed(self.iterstraight(raw))
+        meta = dict()
+        for attr in 'greyscale alpha planes bitdepth interlace'.split():
+            meta[attr] = getattr(self, attr)
+        meta['size'] = (self.width, self.height)
+        for attr in 'gamma transparent background'.split():
+            a = getattr(self, attr, None)
+            if a is not None:
+                meta[attr] = a
+        if self.plte:
+            meta['palette'] = self.palette()
+        return self.width, self.height, pixels, meta
+
+
+    def read_flat(self):
+        """
+        Read a PNG file and decode it into flat row flat pixel format.
+        Returns (*width*, *height*, *pixels*, *metadata*).
+
+        May use excessive memory.
+
+        `pixels` are returned in flat row flat pixel format.
+
+        See also the :meth:`read` method which returns pixels in the
+        more stream-friendly boxed row flat pixel format.
+        """
+
+        x, y, pixel, meta = self.read()
+        arraycode = 'BH'[meta['bitdepth']>8]
+        pixel = array(arraycode, itertools.chain(*pixel))
+        return x, y, pixel, meta
+
+    def palette(self, alpha='natural'):
+        """Returns a palette that is a sequence of 3-tuples or 4-tuples,
+        synthesizing it from the ``PLTE`` and ``tRNS`` chunks.  These
+        chunks should have already been processed (for example, by
+        calling the :meth:`preamble` method).  All the tuples are the
+        same size: 3-tuples if there is no ``tRNS`` chunk, 4-tuples when
+        there is a ``tRNS`` chunk.  Assumes that the image is colour type
+        3 and therefore a ``PLTE`` chunk is required.
+
+        If the `alpha` argument is ``'force'`` then an alpha channel is
+        always added, forcing the result to be a sequence of 4-tuples.
+        """
+
+        if not self.plte:
+            raise FormatError(
+                "Required PLTE chunk is missing in colour type 3 image.")
+        plte = group(array('B', self.plte), 3)
+        if self.trns or alpha == 'force':
+            trns = array('B', self.trns or '')
+            trns.extend([255]*(len(plte)-len(trns)))
+            plte = map(operator.add, plte, group(trns, 1))
+        return plte
+
+    def asDirect(self):
+        """Returns the image data as a direct representation of an
+        ``x * y * planes`` array.  This method is intended to remove the
+        need for callers to deal with palettes and transparency
+        themselves.  Images with a palette (colour type 3)
+        are converted to RGB or RGBA; images with transparency (a
+        ``tRNS`` chunk) are converted to LA or RGBA as appropriate.
+        When returned in this format the pixel values represent the
+        colour value directly without needing to refer to palettes or
+        transparency information.
+
+        Like the :meth:`read` method this method returns a 4-tuple:
+
+        (*width*, *height*, *pixels*, *meta*)
+
+        This method normally returns pixel values with the bit depth
+        they have in the source image, but when the source PNG has an
+        ``sBIT`` chunk it is inspected and can reduce the bit depth of
+        the result pixels; pixel values will be reduced according to
+        the bit depth specified in the ``sBIT`` chunk (PNG nerds should
+        note a single result bit depth is used for all channels; the
+        maximum of the ones specified in the ``sBIT`` chunk.  An RGB565
+        image will be rescaled to 6-bit RGB666).
+
+        The *meta* dictionary that is returned reflects the `direct`
+        format and not the original source image.  For example, an RGB
+        source image with a ``tRNS`` chunk to represent a transparent
+        colour, will have ``planes=3`` and ``alpha=False`` for the
+        source image, but the *meta* dictionary returned by this method
+        will have ``planes=4`` and ``alpha=True`` because an alpha
+        channel is synthesized and added.
+
+        *pixels* is the pixel data in boxed row flat pixel format (just
+        like the :meth:`read` method).
+
+        All the other aspects of the image data are not changed.
+        """
+
+        self.preamble()
+
+        # Simple case, no conversion necessary.
+        if not self.colormap and not self.trns and not self.sbit:
+            return self.read()
+
+        x,y,pixels,meta = self.read()
+
+        if self.colormap:
+            meta['colormap'] = False
+            meta['alpha'] = bool(self.trns)
+            meta['bitdepth'] = 8
+            meta['planes'] = 3 + bool(self.trns)
+            plte = self.palette()
+            def iterpal(pixels):
+                for row in pixels:
+                    row = map(plte.__getitem__, row)
+                    yield array('B', itertools.chain(*row))
+            pixels = iterpal(pixels)
+        elif self.trns:
+            # It would be nice if there was some reasonable way
+            # of doing this without generating a whole load of
+            # intermediate tuples.  But tuples does seem like the
+            # easiest way, with no other way clearly much simpler or
+            # much faster.  (Actually, the L to LA conversion could
+            # perhaps go faster (all those 1-tuples!), but I still
+            # wonder whether the code proliferation is worth it)
+            it = self.transparent
+            maxval = 2**meta['bitdepth']-1
+            planes = meta['planes']
+            meta['alpha'] = True
+            meta['planes'] += 1
+            typecode = 'BH'[meta['bitdepth']>8]
+            def itertrns(pixels):
+                for row in pixels:
+                    # For each row we group it into pixels, then form a
+                    # characterisation vector that says whether each
+                    # pixel is opaque or not.  Then we convert
+                    # True/False to 0/maxval (by multiplication),
+                    # and add it as the extra channel.
+                    row = group(row, planes)
+                    opa = map(it.__ne__, row)
+                    opa = map(maxval.__mul__, opa)
+                    opa = zip(opa) # convert to 1-tuples
+                    yield array(typecode,
+                      itertools.chain(*map(operator.add, row, opa)))
+            pixels = itertrns(pixels)
+        targetbitdepth = None
+        if self.sbit:
+            sbit = struct.unpack('%dB' % len(self.sbit), self.sbit)
+            targetbitdepth = max(sbit)
+            if targetbitdepth > meta['bitdepth']:
+                raise Error('sBIT chunk %r exceeds bitdepth %d' %
+                    (sbit,self.bitdepth))
+            if min(sbit) <= 0:
+                raise Error('sBIT chunk %r has a 0-entry' % sbit)
+            if targetbitdepth == meta['bitdepth']:
+                targetbitdepth = None
+        if targetbitdepth:
+            shift = meta['bitdepth'] - targetbitdepth
+            meta['bitdepth'] = targetbitdepth
+            def itershift(pixels):
+                for row in pixels:
+                    yield map(shift.__rrshift__, row)
+            pixels = itershift(pixels)
+        return x,y,pixels,meta
+
+    def asFloat(self, maxval=1.0):
+        """Return image pixels as per :meth:`asDirect` method, but scale
+        all pixel values to be floating point values between 0.0 and
+        *maxval*.
+        """
+
+        x,y,pixels,info = self.asDirect()
+        sourcemaxval = 2**info['bitdepth']-1
+        del info['bitdepth']
+        info['maxval'] = float(maxval)
+        factor = float(maxval)/float(sourcemaxval)
+        def iterfloat():
+            for row in pixels:
+                yield map(factor.__mul__, row)
+        return x,y,iterfloat(),info
+
+    def _as_rescale(self, get, targetbitdepth):
+        """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`."""
+
+        width,height,pixels,meta = get()
+        maxval = 2**meta['bitdepth'] - 1
+        targetmaxval = 2**targetbitdepth - 1
+        factor = float(targetmaxval) / float(maxval)
+        meta['bitdepth'] = targetbitdepth
+        def iterscale():
+            for row in pixels:
+                yield map(lambda x: int(round(x*factor)), row)
+        if maxval == targetmaxval:
+            return width, height, pixels, meta
+        else:
+            return width, height, iterscale(), meta
+
+    def asRGB8(self):
+        """Return the image data as an RGB pixels with 8-bits per
+        sample.  This is like the :meth:`asRGB` method except that
+        this method additionally rescales the values so that they
+        are all between 0 and 255 (8-bit).  In the case where the
+        source image has a bit depth < 8 the transformation preserves
+        all the information; where the source image has bit depth
+        > 8, then rescaling to 8-bit values loses precision.  No
+        dithering is performed.  Like :meth:`asRGB`, an alpha channel
+        in the source image will raise an exception.
+
+        This function returns a 4-tuple:
+        (*width*, *height*, *pixels*, *metadata*).
+        *width*, *height*, *metadata* are as per the
+        :meth:`read` method.
+        
+        *pixels* is the pixel data in boxed row flat pixel format.
+        """
+
+        return self._as_rescale(self.asRGB, 8)
+
+    def asRGBA8(self):
+        """Return the image data as RGBA pixels with 8-bits per
+        sample.  This method is similar to :meth:`asRGB8` and
+        :meth:`asRGBA`:  The result pixels have an alpha channel, *and*
+        values are rescaled to the range 0 to 255.  The alpha channel is
+        synthesized if necessary (with a small speed penalty).
+        """
+
+        return self._as_rescale(self.asRGBA, 8)
+
+    def asRGB(self):
+        """Return image as RGB pixels.  RGB colour images are passed
+        through unchanged; greyscales are expanded into RGB
+        triplets (there is a small speed overhead for doing this).
+
+        An alpha channel in the source image will raise an
+        exception.
+
+        The return values are as for the :meth:`read` method
+        except that the *metadata* reflect the returned pixels, not the
+        source image.  In particular, for this method
+        ``metadata['greyscale']`` will be ``False``.
+        """
+
+        width,height,pixels,meta = self.asDirect()
+        if meta['alpha']:
+            raise Error("will not convert image with alpha channel to RGB")
+        if not meta['greyscale']:
+            return width,height,pixels,meta
+        meta['greyscale'] = False
+        typecode = 'BH'[meta['bitdepth'] > 8]
+        def iterrgb():
+            for row in pixels:
+                a = array(typecode, [0]) * 3 * width
+                for i in range(3):
+                    a[i::3] = row
+                yield a
+        return width,height,iterrgb(),meta
+
+    def asRGBA(self):
+        """Return image as RGBA pixels.  Greyscales are expanded into
+        RGB triplets; an alpha channel is synthesized if necessary.
+        The return values are as for the :meth:`read` method
+        except that the *metadata* reflect the returned pixels, not the
+        source image.  In particular, for this method
+        ``metadata['greyscale']`` will be ``False``, and
+        ``metadata['alpha']`` will be ``True``.
+        """
+
+        width,height,pixels,meta = self.asDirect()
+        if meta['alpha'] and not meta['greyscale']:
+            return width,height,pixels,meta
+        typecode = 'BH'[meta['bitdepth'] > 8]
+        maxval = 2**meta['bitdepth'] - 1
+        maxbuffer = struct.pack('=' + typecode, maxval) * 4 * width
+        def newarray():
+            return array(typecode, maxbuffer)
+
+        if meta['alpha'] and meta['greyscale']:
+            # LA to RGBA
+            def convert():
+                for row in pixels:
+                    # Create a fresh target row, then copy L channel
+                    # into first three target channels, and A channel
+                    # into fourth channel.
+                    a = newarray()
+                    pngfilters.convert_la_to_rgba(row, a)
+                    yield a
+        elif meta['greyscale']:
+            # L to RGBA
+            def convert():
+                for row in pixels:
+                    a = newarray()
+                    pngfilters.convert_l_to_rgba(row, a)
+                    yield a
+        else:
+            assert not meta['alpha'] and not meta['greyscale']
+            # RGB to RGBA
+            def convert():
+                for row in pixels:
+                    a = newarray()
+                    pngfilters.convert_rgb_to_rgba(row, a)
+                    yield a
+        meta['alpha'] = True
+        meta['greyscale'] = False
+        return width,height,convert(),meta
+
+
+def check_bitdepth_colortype(bitdepth, colortype):
+    """Check that `bitdepth` and `colortype` are both valid,
+    and specified in a valid combination. Returns if valid,
+    raise an Exception if not valid.
+    """
+
+    if bitdepth not in (1,2,4,8,16):
+        raise FormatError("invalid bit depth %d" % bitdepth)
+    if colortype not in (0,2,3,4,6):
+        raise FormatError("invalid colour type %d" % colortype)
+    # Check indexed (palettized) images have 8 or fewer bits
+    # per pixel; check only indexed or greyscale images have
+    # fewer than 8 bits per pixel.
+    if colortype & 1 and bitdepth > 8:
+        raise FormatError(
+          "Indexed images (colour type %d) cannot"
+          " have bitdepth > 8 (bit depth %d)."
+          " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
+          % (bitdepth, colortype))
+    if bitdepth < 8 and colortype not in (0,3):
+        raise FormatError("Illegal combination of bit depth (%d)"
+          " and colour type (%d)."
+          " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
+          % (bitdepth, colortype))
+
+
+def isinteger(x):
+    try:
+        return int(x) == x
+    except (TypeError, ValueError):
+        return False
+
+
+class pngfilters(object):
+    def undo_filter_sub(filter_unit, scanline, previous, result):
+        """Undo sub filter."""
+
+        ai = 0
+        # Loops starts at index fu.  Observe that the initial part
+        # of the result is already filled in correctly with
+        # scanline.
+        for i in range(filter_unit, len(result)):
+            x = scanline[i]
+            a = result[ai]
+            result[i] = (x + a) & 0xff
+            ai += 1
+    undo_filter_sub = staticmethod(undo_filter_sub)
+
+    def undo_filter_up(filter_unit, scanline, previous, result):
+        """Undo up filter."""
+
+        for i in range(len(result)):
+            x = scanline[i]
+            b = previous[i]
+            result[i] = (x + b) & 0xff
+    undo_filter_up = staticmethod(undo_filter_up)
+
+    def undo_filter_average(filter_unit, scanline, previous, result):
+        """Undo up filter."""
+
+        ai = -filter_unit
+        for i in range(len(result)):
+            x = scanline[i]
+            if ai < 0:
+                a = 0
+            else:
+                a = result[ai]
+            b = previous[i]
+            result[i] = (x + ((a + b) >> 1)) & 0xff
+            ai += 1
+    undo_filter_average = staticmethod(undo_filter_average)
+
+    def undo_filter_paeth(filter_unit, scanline, previous, result):
+        """Undo Paeth filter."""
+
+        # Also used for ci.
+        ai = -filter_unit
+        for i in range(len(result)):
+            x = scanline[i]
+            if ai < 0:
+                a = c = 0
+            else:
+                a = result[ai]
+                c = previous[ai]
+            b = previous[i]
+            p = a + b - c
+            pa = abs(p - a)
+            pb = abs(p - b)
+            pc = abs(p - c)
+            if pa <= pb and pa <= pc:
+                pr = a
+            elif pb <= pc:
+                pr = b
+            else:
+                pr = c
+            result[i] = (x + pr) & 0xff
+            ai += 1
+    undo_filter_paeth = staticmethod(undo_filter_paeth)
+
+    def convert_la_to_rgba(row, result):
+        for i in range(3):
+            result[i::4] = row[0::2]
+        result[3::4] = row[1::2]
+    convert_la_to_rgba = staticmethod(convert_la_to_rgba)
+
+    def convert_l_to_rgba(row, result):
+        """Convert a grayscale image to RGBA. This method assumes
+        the alpha channel in result is already correctly
+        initialized.
+        """
+        for i in range(3):
+            result[i::4] = row
+    convert_l_to_rgba = staticmethod(convert_l_to_rgba)
+
+    def convert_rgb_to_rgba(row, result):
+        """Convert an RGB image to RGBA. This method assumes the
+        alpha channel in result is already correctly initialized.
+        """
+        for i in range(3):
+            result[i::4] = row[i::3]
+    convert_rgb_to_rgba = staticmethod(convert_rgb_to_rgba)
diff --git a/vispy/ext/py24_ordereddict.py b/vispy/ext/py24_ordereddict.py
new file mode 100644
index 0000000..b6f4193
--- /dev/null
+++ b/vispy/ext/py24_ordereddict.py
@@ -0,0 +1,267 @@
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+
+# MIT-licensed from:
+# http://code.activestate.com/recipes/576693/
+# http://docs.python.org/2/library/collections.html#ordereddict-objects
+
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+
+class OrderedDict(dict):
+
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular
+    # dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value
+        # pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor
+        # nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) items in od'
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+
+        '''
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    # let subclasses override update without breaking __init__
+    __update = update
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self) == len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
diff --git a/vispy/util/six.py b/vispy/ext/six.py
similarity index 55%
rename from vispy/util/six.py
rename to vispy/ext/six.py
index eae3145..b3595a4 100644
--- a/vispy/util/six.py
+++ b/vispy/ext/six.py
@@ -2,32 +2,34 @@
 
 # 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:
+# 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.
+# 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"
+__version__ = "1.4.1"
 
 
-# True if we are running on Python 3.
+# Useful for very coarse version differentiation.
+PY2 = sys.version_info[0] == 2
 PY3 = sys.version_info[0] == 3
 
 if PY3:
@@ -61,7 +63,7 @@ else:
         else:
             # 64-bit
             MAXSIZE = int((1 << 63) - 1)
-            del X
+        del X
 
 
 def _add_doc(func, doc):
@@ -136,13 +138,17 @@ class _MovedItems(types.ModuleType):
 _moved_attributes = [
     MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
     MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+    MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
     MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
     MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+    MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
     MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
     MovedAttribute("reduce", "__builtin__", "functools"),
     MovedAttribute("StringIO", "StringIO", "io"),
+    MovedAttribute("UserString", "UserString", "collections"),
     MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
     MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+    MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
 
     MovedModule("builtins", "__builtin__"),
     MovedModule("configparser", "ConfigParser"),
@@ -179,6 +185,9 @@ _moved_attributes = [
     MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
     MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
                 "tkinter.simpledialog"),
+    MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+    MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+    MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
     MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
     MovedModule("winreg", "_winreg"),
 ]
@@ -186,7 +195,144 @@ for attr in _moved_attributes:
     setattr(_MovedItems, attr.name, attr)
 del attr
 
-moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
+moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves")
+
+
+
+class Module_six_moves_urllib_parse(types.ModuleType):
+    """Lazy loading of moved objects in six.moves.urllib_parse"""
+
+
+_urllib_parse_moved_attributes = [
+    MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+    MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+    MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+    MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+    MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+    MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+    MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+    MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+    MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+    MovedAttribute("quote", "urllib", "urllib.parse"),
+    MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+    MovedAttribute("unquote", "urllib", "urllib.parse"),
+    MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+    MovedAttribute("urlencode", "urllib", "urllib.parse"),
+]
+for attr in _urllib_parse_moved_attributes:
+    setattr(Module_six_moves_urllib_parse, attr.name, attr)
+del attr
+
+sys.modules[__name__ + ".moves.urllib_parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse")
+sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib.parse")
+
+
+class Module_six_moves_urllib_error(types.ModuleType):
+    """Lazy loading of moved objects in six.moves.urllib_error"""
+
+
+_urllib_error_moved_attributes = [
+    MovedAttribute("URLError", "urllib2", "urllib.error"),
+    MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+    MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+]
+for attr in _urllib_error_moved_attributes:
+    setattr(Module_six_moves_urllib_error, attr.name, attr)
+del attr
+
+sys.modules[__name__ + ".moves.urllib_error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib_error")
+sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error")
+
+
+class Module_six_moves_urllib_request(types.ModuleType):
+    """Lazy loading of moved objects in six.moves.urllib_request"""
+
+
+_urllib_request_moved_attributes = [
+    MovedAttribute("urlopen", "urllib2", "urllib.request"),
+    MovedAttribute("install_opener", "urllib2", "urllib.request"),
+    MovedAttribute("build_opener", "urllib2", "urllib.request"),
+    MovedAttribute("pathname2url", "urllib", "urllib.request"),
+    MovedAttribute("url2pathname", "urllib", "urllib.request"),
+    MovedAttribute("getproxies", "urllib", "urllib.request"),
+    MovedAttribute("Request", "urllib2", "urllib.request"),
+    MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+    MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+    MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+    MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+    MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+    MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+    MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+    MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+    MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+    MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+    MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+    MovedAttribute("URLopener", "urllib", "urllib.request"),
+    MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+]
+for attr in _urllib_request_moved_attributes:
+    setattr(Module_six_moves_urllib_request, attr.name, attr)
+del attr
+
+sys.modules[__name__ + ".moves.urllib_request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib_request")
+sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request")
+
+
+class Module_six_moves_urllib_response(types.ModuleType):
+    """Lazy loading of moved objects in six.moves.urllib_response"""
+
+
+_urllib_response_moved_attributes = [
+    MovedAttribute("addbase", "urllib", "urllib.response"),
+    MovedAttribute("addclosehook", "urllib", "urllib.response"),
+    MovedAttribute("addinfo", "urllib", "urllib.response"),
+    MovedAttribute("addinfourl", "urllib", "urllib.response"),
+]
+for attr in _urllib_response_moved_attributes:
+    setattr(Module_six_moves_urllib_response, attr.name, attr)
+del attr
+
+sys.modules[__name__ + ".moves.urllib_response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib_response")
+sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response")
+
+
+class Module_six_moves_urllib_robotparser(types.ModuleType):
+    """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+
+
+_urllib_robotparser_moved_attributes = [
+    MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+]
+for attr in _urllib_robotparser_moved_attributes:
+    setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+del attr
+
+sys.modules[__name__ + ".moves.urllib_robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib_robotparser")
+sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser")
+
+
+class Module_six_moves_urllib(types.ModuleType):
+    """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+    parse = sys.modules[__name__ + ".moves.urllib_parse"]
+    error = sys.modules[__name__ + ".moves.urllib_error"]
+    request = sys.modules[__name__ + ".moves.urllib_request"]
+    response = sys.modules[__name__ + ".moves.urllib_response"]
+    robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"]
+
+
+sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib")
 
 
 def add_move(move):
@@ -252,11 +398,16 @@ if PY3:
     def get_unbound_function(unbound):
         return unbound
 
+    create_bound_method = types.MethodType
+
     Iterator = object
 else:
     def get_unbound_function(unbound):
         return unbound.im_func
 
+    def create_bound_method(func, obj):
+        return types.MethodType(func, obj, obj.__class__)
+
     class Iterator(object):
 
         def next(self):
@@ -297,12 +448,16 @@ if PY3:
         return s.encode("latin-1")
     def u(s):
         return s
+    unichr = chr
     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")
+    byte2int = operator.itemgetter(0)
+    indexbytes = operator.getitem
+    iterbytes = iter
     import io
     StringIO = io.StringIO
     BytesIO = io.BytesIO
@@ -311,7 +466,14 @@ else:
         return s
     def u(s):
         return unicode(s, "unicode_escape")
+    unichr = unichr
     int2byte = chr
+    def byte2int(bs):
+        return ord(bs[0])
+    def indexbytes(buf, i):
+        return ord(buf[i])
+    def iterbytes(buf):
+        return (ord(byte) for byte in buf)
     import StringIO
     StringIO = BytesIO = StringIO.StringIO
 _add_doc(b, """Byte literal""")
@@ -399,6 +561,17 @@ else:
 _add_doc(reraise, """Reraise an exception.""")
 
 
-def with_metaclass(meta, base=object):
+def with_metaclass(meta, *bases):
     """Create a base class with a metaclass."""
-    return meta("NewBase", (base,), {})
+    return meta("NewBase", bases, {})
+
+def add_metaclass(metaclass):
+    """Class decorator for creating a class with a metaclass."""
+    def wrapper(cls):
+        orig_vars = cls.__dict__.copy()
+        orig_vars.pop('__dict__', None)
+        orig_vars.pop('__weakref__', None)
+        for slots_var in orig_vars.get('__slots__', ()):
+            orig_vars.pop(slots_var)
+        return metaclass(cls.__name__, cls.__bases__, orig_vars)
+    return wrapper
\ No newline at end of file
diff --git a/vispy/geometry/__init__.py b/vispy/geometry/__init__.py
new file mode 100644
index 0000000..6d92c0d
--- /dev/null
+++ b/vispy/geometry/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+This module implements classes and methods for handling geometric data.
+"""
+
+from __future__ import division
+
+__all__ = ['MeshData', 'PolygonData', 'Rect', 'Triangulation', 'create_cube',
+           'create_cylinder', 'create_sphere']
+
+from .polygon import PolygonData  # noqa
+from .meshdata import MeshData  # noqa
+from .rect import Rect  # noqa
+from .triangulation import Triangulation  # noqa
+from .calculations import _calculate_normals, _fast_cross_3d  # noqa
+from .generation import create_cube, create_cylinder, create_sphere  # noqa
diff --git a/vispy/geometry/_triangulation_debugger.py b/vispy/geometry/_triangulation_debugger.py
new file mode 100644
index 0000000..041ae87
--- /dev/null
+++ b/vispy/geometry/_triangulation_debugger.py
@@ -0,0 +1,171 @@
+# -*- coding: utf8 -*- 
+"""
+Debugging system for Triangulation class. Displays stepwise visual 
+representation of the algorithm. 
+
+This system currently requires pyqtgraph for its visual output.
+"""
+from __future__ import division
+
+import numpy as np
+import time
+
+from vispy.util.geometry.triangulation import Triangulation
+
+
+class DebugTriangulation(Triangulation):
+    """ 
+    Visualize triangulation process stepwise to aid in debugging.
+    
+    *interval* specifies the diration to wait before drawing each update in
+    the triangulation procedure. Negative values cause the display to wait
+    until the user clicks on the window for each update.
+    
+    *skip* causes the display to immediately process the first N events
+    before pausing.
+    """
+    def __init__(self, pts, edges, interval=0.01, skip=0):
+        self.interval = interval
+        self.iteration = 0
+        self.skip = skip 
+        
+        Triangulation.__init__(self, pts, edges)
+        
+        # visual #debugging: draw edges, front, triangles
+        self.win = pg.plot()
+        self.graph = pg.GraphItem(pos=pts.copy(), adj=edges.copy(), 
+                                  pen={'width': 3, 'color': (0, 100, 0)})
+        self.win.addItem(self.graph)
+        self.front_line = pg.PlotCurveItem(pen={'width': 2, 
+                                                'dash': [5, 5], 
+                                                'color': 'y'})
+        self.win.addItem(self.front_line)
+        self.tri_shapes = {}
+        
+        self.nextStep = False
+        self.win.scene().sigMouseClicked.connect(self.mouseClicked)
+
+    def mouseClicked(self):
+        self.nextStep = True
+        
+    def draw_state(self):
+        global app
+        print("State %s" % self.iteration)
+        self.iteration += 1
+        if self.iteration <= self.skip:
+            return
+        
+        front_pts = self.pts[np.array(self.front)]
+        self.front_line.setData(front_pts[:, 0], front_pts[:, 1])
+        self.graph.setData(pos=self.pts, adj=self.edges) 
+        
+        # Auto-advance on timer
+        if self.interval < 0:
+            #Advance once per click
+            while True:
+                app.processEvents()
+                time.sleep(0.01)
+                if self.nextStep:
+                    self.nextStep = False
+                    break
+        else:
+            # sleep, but keep ui responsive
+            for i in range(int(self.interval / 0.01)):
+                app.processEvents()
+                time.sleep(0.01)
+            
+    def draw_tri(self, tri, source=None):
+        # assign triangle color based on the source that generated it
+        color = {
+            None: (0, 255, 255, 50),
+            'smooth1': (0, 255, 0, 50),
+            'fill_hull': (255, 255, 0, 50),
+            'edge_event': (100, 100, 255, 100),
+        }[source]
+        
+        tpts = self.pts[np.array(tri)]
+        path = pg.arrayToQPath(tpts[:, 0], tpts[:, 1])
+        shape = pg.QtGui.QGraphicsPathItem(path)
+        shape.setPen(pg.mkPen(255, 255, 255, 100))
+        brush = pg.mkBrush(color)
+        shape.setBrush(brush)
+        self.win.addItem(shape)
+        self.tri_shapes[tri] = shape
+        self.draw_state()
+
+    def undraw_tri(self, tri):
+        shape = self.tri_shapes.pop(tri)
+        self.win.removeItem(shape)
+        self.draw_state()
+        
+    def add_tri(self, *args, **kwds):
+        Triangulation.add_tri(self, *args, **kwds)
+        self.draw_tri(list(self.tris.keys())[-1], 
+                      source=kwds.get('source', None))
+    
+    def remove_tri(self, *args, **kwds):
+        k = Triangulation.remove_tri(self, *args, **kwds)
+        self.undraw_tri(k)
+
+    def edge_event(self, *args, **kwds):
+        self.draw_state()
+        Triangulation.edge_event(self, *args, **kwds)
+        self.draw_state()
+
+
+if __name__ == '__main__':
+    import pyqtgraph as pg
+    
+    app = pg.mkQApp()
+    
+    #user input data - points and constraining edges
+    
+    #
+    #  Test 1
+    #
+    pts = [(0, 0),
+           (10, 0),
+           (10, 10),
+           (20, 10),
+           (20, 20),
+           (25, 20),
+           (25, 25),
+           (20, 25),
+           (20, 20),
+           (10, 17),
+           (5, 25),
+           (9, 30),
+           (6, 15),
+           (15, 12.5),
+           (0, 5)]
+    l = len(pts)
+    edges = [(i, (i+1) % l) for i in range(l)]
+    pts += [(21, 21),
+            (24, 21),
+            (24, 24),
+            (21, 24)]
+    edges += [(l,   l+1),
+              (l+1, l+2),
+              (l+2, l+3),
+              (l+3, l)]
+
+    pts = np.array(pts, dtype=float)
+    edges = np.array(edges, dtype=int)
+
+    #t = DebugTriangulation(pts, edges, interval=-1, skip=19570)
+    #t.triangulate()
+
+    # make lines that are entirely vertical / horizontal
+    np.random.seed(1)
+    N = 100
+    pts = [[0, 0]]
+    for i in range(N - 1):
+        p = pts[-1][:]
+        p[i % 2] += np.random.normal()
+        pts.append(p)
+    pts = np.array(pts)
+    edges = np.zeros((N, 2), dtype=int)
+    edges[:, 0] = np.arange(N)
+    edges[:, 1] = np.arange(1, N + 1) % N
+    t = DebugTriangulation(pts, edges)
+    t.triangulate()
diff --git a/vispy/geometry/calculations.py b/vispy/geometry/calculations.py
new file mode 100644
index 0000000..590351b
--- /dev/null
+++ b/vispy/geometry/calculations.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""Miscellaneous functions
+"""
+
+import numpy as np
+
+
+###############################################################################
+# These fast normal calculation routines are adapted from mne-python
+
+def _fast_cross_3d(x, y):
+    """Compute cross product between list of 3D vectors
+
+    Much faster than np.cross() when the number of cross products
+    becomes large (>500). This is because np.cross() methods become
+    less memory efficient at this stage.
+
+    Parameters
+    ----------
+    x : array
+        Input array 1.
+    y : array
+        Input array 2.
+
+    Returns
+    -------
+    z : array
+        Cross product of x and y.
+
+    Notes
+    -----
+    x and y must both be 2D row vectors. One must have length 1, or both
+    lengths must match.
+    """
+    assert x.ndim == 2
+    assert y.ndim == 2
+    assert x.shape[1] == 3
+    assert y.shape[1] == 3
+    assert (x.shape[0] == 1 or y.shape[0] == 1) or x.shape[0] == y.shape[0]
+    if max([x.shape[0], y.shape[0]]) >= 500:
+        return np.c_[x[:, 1] * y[:, 2] - x[:, 2] * y[:, 1],
+                     x[:, 2] * y[:, 0] - x[:, 0] * y[:, 2],
+                     x[:, 0] * y[:, 1] - x[:, 1] * y[:, 0]]
+    else:
+        return np.cross(x, y)
+
+
+def _calculate_normals(rr, tris):
+    """Efficiently compute vertex normals for triangulated surface"""
+    # ensure highest precision for our summation/vectorization "trick"
+    rr = rr.astype(np.float64)
+    # first, compute triangle normals
+    r1 = rr[tris[:, 0], :]
+    r2 = rr[tris[:, 1], :]
+    r3 = rr[tris[:, 2], :]
+    tri_nn = _fast_cross_3d((r2 - r1), (r3 - r1))
+
+    #   Triangle normals and areas
+    size = np.sqrt(np.sum(tri_nn * tri_nn, axis=1))
+    size[size == 0] = 1.0  # prevent ugly divide-by-zero
+    tri_nn /= size[:, np.newaxis]
+
+    npts = len(rr)
+
+    # the following code replaces this, but is faster (vectorized):
+    #
+    # for p, verts in enumerate(tris):
+    #     nn[verts, :] += tri_nn[p, :]
+    #
+    nn = np.zeros((npts, 3))
+    for verts in tris.T:  # note this only loops 3x (number of verts per tri)
+        for idx in range(3):  # x, y, z
+            nn[:, idx] += np.bincount(verts, tri_nn[:, idx], minlength=npts)
+    size = np.sqrt(np.sum(nn * nn, axis=1))
+    size[size == 0] = 1.0  # prevent ugly divide-by-zero
+    nn /= size[:, np.newaxis]
+    return nn
diff --git a/vispy/geometry/generation.py b/vispy/geometry/generation.py
new file mode 100644
index 0000000..ae81336
--- /dev/null
+++ b/vispy/geometry/generation.py
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Nicolas P .Rougier
+# Date:   04/03/2014
+# -----------------------------------------------------------------------------
+import numpy as np
+
+from .meshdata import MeshData
+
+
+def create_cube():
+    """ Generate vertices & indices for a filled and outlined cube
+
+    Returns
+    -------
+    vertices : array
+        Array of vertices suitable for use as a VertexBuffer.
+    filled : array
+        Indices to use to produce a filled cube.
+    outline : array
+        Indices to use to produce an outline of the cube.
+    """
+    vtype = [('position', np.float32, 3),
+             ('texcoord', np.float32, 2),
+             ('normal', np.float32, 3),
+             ('color',    np.float32, 4)]
+    itype = np.uint32
+
+    # Vertices positions
+    p = np.array([[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 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0],
+                  [-1, 0, 1], [0, -1, 0], [0, 0, -1]])
+
+    # Vertice colors
+    c = np.array([[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]])
+
+    # Texture coords
+    t = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
+
+    faces_p = [0, 1, 2, 3,
+               0, 3, 4, 5,
+               0, 5, 6, 1,
+               1, 6, 7, 2,
+               7, 4, 3, 2,
+               4, 7, 6, 5]
+    faces_c = [0, 1, 2, 3,
+               0, 3, 4, 5,
+               0, 5, 6, 1,
+               1, 6, 7, 2,
+               7, 4, 3, 2,
+               4, 7, 6, 5]
+    faces_n = [0, 0, 0, 0,
+               1, 1, 1, 1,
+               2, 2, 2, 2,
+               3, 3, 3, 3,
+               4, 4, 4, 4,
+               5, 5, 5, 5]
+    faces_t = [0, 1, 2, 3,
+               0, 1, 2, 3,
+               0, 1, 2, 3,
+               3, 2, 1, 0,
+               0, 1, 2, 3,
+               0, 1, 2, 3]
+
+    vertices = np.zeros(24, vtype)
+    vertices['position'] = p[faces_p]
+    vertices['normal'] = n[faces_n]
+    vertices['color'] = c[faces_c]
+    vertices['texcoord'] = t[faces_t]
+
+    filled = np.resize(
+        np.array([0, 1, 2, 0, 2, 3], dtype=itype), 6 * (2 * 3))
+    filled += np.repeat(4 * np.arange(6, dtype=itype), 6)
+
+    outline = np.resize(
+        np.array([0, 1, 1, 2, 2, 3, 3, 0], dtype=itype), 6 * (2 * 4))
+    outline += np.repeat(4 * np.arange(6, dtype=itype), 8)
+
+    return vertices, filled, outline
+
+
+def create_sphere(rows, cols, radius=1.0, offset=True):
+    """Create a sphere
+
+    Parameters
+    ----------
+    rows : int
+        Number of rows.
+    cols : int
+        Number of columns.
+    radius : float
+        Sphere radius.
+    offset : bool
+        Rotate each row by half a column.
+
+    Returns
+    -------
+    sphere : MeshData
+        Vertices and faces computed for a spherical surface.
+    """
+    verts = np.empty((rows+1, cols, 3), dtype=np.float32)
+
+    ## compute vertices
+    phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1)
+    s = radius * np.sin(phi)
+    verts[..., 2] = radius * np.cos(phi)
+    th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols))
+    if offset:
+        # rotate each row by 1/2 column
+        th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1, 1))
+    verts[..., 0] = s * np.cos(th)
+    verts[..., 1] = s * np.sin(th)
+    ## remove redundant vertices from top and bottom
+    verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)]
+
+    ## compute faces
+    faces = np.empty((rows*cols*2, 3), dtype=np.uint32)
+    rowtemplate1 = (((np.arange(cols).reshape(cols, 1) +
+                      np.array([[1, 0, 0]])) % cols)
+                    + np.array([[0, 0, cols]]))
+    rowtemplate2 = (((np.arange(cols).reshape(cols, 1) +
+                      np.array([[1, 0, 1]])) % cols)
+                    + np.array([[0, cols, cols]]))
+    for row in range(rows):
+        start = row * cols * 2
+        faces[start:start+cols] = rowtemplate1 + row * cols
+        faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols
+    ## cut off zero-area triangles at top and bottom
+    faces = faces[cols:-cols]
+
+    ## adjust for redundant vertices that were removed from top and bottom
+    vmin = cols-1
+    faces[faces < vmin] = vmin
+    faces -= vmin
+    vmax = verts.shape[0]-1
+    faces[faces > vmax] = vmax
+    return MeshData(vertices=verts, faces=faces)
+
+
+def create_cylinder(rows, cols, radius=[1.0, 1.0], length=1.0, offset=False):
+    """Create a cylinder
+
+    Parameters
+    ----------
+    rows : int
+        Number of rows.
+    cols : int
+        Number of columns.
+    radius : tuple of float
+        Cylinder radii.
+    length : float
+        Length of the cylinder.
+    offset : bool
+        Rotate each row by half a column.
+
+    Returns
+    -------
+    cylinder : MeshData
+        Vertices and faces computed for a cylindrical surface.
+    """
+    verts = np.empty((rows+1, cols, 3), dtype=np.float32)
+    if isinstance(radius, int):
+        radius = [radius, radius]  # convert to list
+    ## compute vertices
+    th = np.linspace(2 * np.pi, 0, cols).reshape(1, cols)
+    # radius as a function of z
+    r = np.linspace(radius[0], radius[1], num=rows+1,
+                    endpoint=True).reshape(rows+1, 1)
+    verts[..., 2] = np.linspace(0, length, num=rows+1,
+                                endpoint=True).reshape(rows+1, 1)  # z
+    if offset:
+        ## rotate each row by 1/2 column
+        th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1, 1))
+    verts[..., 0] = r * np.cos(th)  # x = r cos(th)
+    verts[..., 1] = r * np.sin(th)  # y = r sin(th)
+    # just reshape: no redundant vertices...
+    verts = verts.reshape((rows+1)*cols, 3)
+    ## compute faces
+    faces = np.empty((rows*cols*2, 3), dtype=np.uint)
+    rowtemplate1 = (((np.arange(cols).reshape(cols, 1) +
+                      np.array([[0, 1, 0]])) % cols)
+                    + np.array([[0, 0, cols]]))
+    rowtemplate2 = (((np.arange(cols).reshape(cols, 1) +
+                      np.array([[0, 1, 1]])) % cols)
+                    + np.array([[cols, 0, cols]]))
+    for row in range(rows):
+        start = row * cols * 2
+        faces[start:start+cols] = rowtemplate1 + row * cols
+        faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols
+
+    return MeshData(vertices=verts, faces=faces)
diff --git a/vispy/geometry/isocurve.py b/vispy/geometry/isocurve.py
new file mode 100644
index 0000000..09a816f
--- /dev/null
+++ b/vispy/geometry/isocurve.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from __future__ import division
+
+import numpy as np
+
+
+def isocurve(data, level, connected=False, extend_to_edge=False):
+    """
+    Generate isocurve from 2D data using marching squares algorithm.
+
+    Parameters
+    ----------
+    data : ndarray
+        2D numpy array of scalar values
+    level : float
+        The level at which to generate an isosurface
+    connected : bool
+        If False, return a single long list of point pairs
+        If True, return multiple long lists of connected point 
+        locations. (This is slower but better for drawing 
+        continuous lines)
+    extend_to_edge : bool
+        If True, extend the curves to reach the exact edges of 
+        the data. 
+    """    
+    # This function is SLOW; plenty of room for optimization here.
+    
+    if extend_to_edge:
+        d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype)
+        d2[1:-1, 1:-1] = data
+        d2[0, 1:-1] = data[0]
+        d2[-1, 1:-1] = data[-1]
+        d2[1:-1, 0] = data[:, 0]
+        d2[1:-1, -1] = data[:, -1]
+        d2[0, 0] = d2[0, 1]
+        d2[0, -1] = d2[1, -1]
+        d2[-1, 0] = d2[-1, 1]
+        d2[-1, -1] = d2[-1, -2]
+        data = d2
+    
+    side_table = [
+        [],
+        [0, 1],
+        [1, 2],
+        [0, 2],
+        [0, 3],
+        [1, 3],
+        [0, 1, 2, 3],
+        [2, 3],
+        [2, 3],
+        [0, 1, 2, 3],
+        [1, 3],
+        [0, 3],
+        [0, 2],
+        [1, 2],
+        [0, 1],
+        []
+    ]
+    
+    edge_key = [
+        [(0, 1), (0, 0)],
+        [(0, 0), (1, 0)],
+        [(1, 0), (1, 1)],
+        [(1, 1), (0, 1)]
+    ]
+    
+    level = float(level)
+    lines = []
+    
+    # mark everything below the isosurface level
+    mask = data < level
+    
+    ## make four sub-fields and compute indexes for grid cells
+    index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte)
+    fields = np.empty((2, 2), dtype=object)
+    slices = [slice(0, -1), slice(1, None)]
+    for i in [0, 1]:
+        for j in [0, 1]:
+            fields[i, j] = mask[slices[i], slices[j]]
+            vertIndex = i+2*j
+            index += fields[i, j] * 2**vertIndex
+    
+    # add lines
+    for i in range(index.shape[0]):                 # data x-axis
+        for j in range(index.shape[1]):             # data y-axis     
+            sides = side_table[index[i, j]]
+            for l in range(0, len(sides), 2):     # faces for this grid cell
+                edges = sides[l:l+2]
+                pts = []
+                for m in [0, 1]:      # points in this face
+                    # p1, p2 are points at either side of an edge
+                    p1 = edge_key[edges[m]][0] 
+                    p2 = edge_key[edges[m]][1]
+                    # v1 and v2 are the values at p1 and p2
+                    v1 = data[i+p1[0], j+p1[1]] 
+                    v2 = data[i+p2[0], j+p2[1]]
+                    f = (level-v1) / (v2-v1)
+                    fi = 1.0 - f
+                    # interpolate between corners
+                    p = (p1[0]*fi + p2[0]*f + i + 0.5, 
+                         p1[1]*fi + p2[1]*f + j + 0.5)
+                    if extend_to_edge:
+                        # check bounds
+                        p = (min(data.shape[0]-2, max(0, p[0]-1)),
+                             min(data.shape[1]-2, max(0, p[1]-1)))
+                    if connected:
+                        gridKey = (i + (1 if edges[m] == 2 else 0), 
+                                   j + (1 if edges[m] == 3 else 0), 
+                                   edges[m] % 2)
+                        # give the actual position and a key identifying the 
+                        # grid location (for connecting segments)
+                        pts.append((p, gridKey))
+                    else:
+                        pts.append(p)
+                
+                lines.append(pts)
+
+    if not connected:
+        return lines
+                
+    # turn disjoint list of segments into continuous lines
+
+    points = {}  # maps each point to its connections
+    for a, b in lines:
+        if a[1] not in points:
+            points[a[1]] = []
+        points[a[1]].append([a, b])
+        if b[1] not in points:
+            points[b[1]] = []
+        points[b[1]].append([b, a])
+
+    # rearrange into chains
+    for k in list(points.keys()):
+        try:
+            chains = points[k]
+        except KeyError:  # already used this point elsewhere
+            continue
+        for chain in chains:
+            x = None
+            while True:
+                if x == chain[-1][1]:
+                    break  # nothing left to do on this chain
+                    
+                x = chain[-1][1]
+                if x == k:
+                    # chain has looped; we're done and can ignore the opposite
+                    # chain
+                    break
+                y = chain[-2][1]
+                connects = points[x]
+                for conn in connects[:]:
+                    if conn[1][1] != y:
+                        chain.extend(conn[1:])
+                del points[x]
+            if chain[0][1] == chain[-1][1]:
+                # looped chain; no need to continue the other direction
+                chains.pop()
+                break
+                
+    # extract point locations 
+    lines = []
+    for chain in points.values():
+        if len(chain) == 2:
+            # join together ends of chain
+            chain = chain[1][1:][::-1] + chain[0]
+        else:
+            chain = chain[0]
+        lines.append([pt[0] for pt in chain])
+    
+    return lines  # a list of pairs of points
diff --git a/vispy/geometry/isosurface.py b/vispy/geometry/isosurface.py
new file mode 100644
index 0000000..550db48
--- /dev/null
+++ b/vispy/geometry/isosurface.py
@@ -0,0 +1,471 @@
+import numpy as np
+
+_data_cache = None
+
+
+def isosurface(data, level):
+    """
+    Generate isosurface from volumetric data using marching cubes algorithm.
+    See Paul Bourke, "Polygonising a Scalar Field"  
+    (http://paulbourke.net/geometry/polygonise/)
+    
+    *data*   3D numpy array of scalar values
+    *level*  The level at which to generate an isosurface
+    
+    Returns an array of vertex coordinates (Nv, 3) and an array of 
+    per-face vertex indexes (Nf, 3)    
+    """
+    # For improvement, see:
+    # 
+    # Efficient implementation of Marching Cubes' cases with topological 
+    # guarantees.
+    # Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares.
+    # Journal of Graphics Tools 8(2): pp. 1-15 (december 2003)
+    
+    (face_shift_tables, edge_shifts, 
+     edge_table, n_table_faces) = _get_data_cache()
+    
+    ## mark everything below the isosurface level
+    mask = data < level
+    
+    ### make eight sub-fields and compute indexes for grid cells
+    index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte)
+    fields = np.empty((2, 2, 2), dtype=object)
+    slices = [slice(0, -1), slice(1, None)]
+    for i in [0, 1]:
+        for j in [0, 1]:
+            for k in [0, 1]:
+                fields[i, j, k] = mask[slices[i], slices[j], slices[k]]
+                # this is just to match Bourk's vertex numbering scheme:
+                vertIndex = i - 2*j*i + 3*j + 4*k
+                index += fields[i, j, k] * 2**vertIndex
+    
+    ### Generate table of edges that have been cut
+    cut_edges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32)
+    edges = edge_table[index]
+    for i, shift in enumerate(edge_shifts[:12]):        
+        slices = [slice(shift[j], cut_edges.shape[j]+(shift[j]-1)) 
+                  for j in range(3)]
+        cut_edges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i
+    
+    # for each cut edge, interpolate to see where exactly the edge is cut and 
+    # generate vertex positions
+    m = cut_edges > 0
+    vertex_inds = np.argwhere(m)  # argwhere is slow!
+    vertexes = vertex_inds[:, :3].astype(np.float32)
+    dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2])
+    
+    ## re-use the cut_edges array as a lookup table for vertex IDs
+    cut_edges[vertex_inds[:, 0], 
+              vertex_inds[:, 1], 
+              vertex_inds[:, 2], 
+              vertex_inds[:, 3]] = np.arange(vertex_inds.shape[0])
+    
+    for i in [0, 1, 2]:
+        vim = vertex_inds[:, 3] == i
+        vi = vertex_inds[vim, :3]
+        vi_flat = (vi * (np.array(data.strides[:3]) // 
+                         data.itemsize)[np.newaxis, :]).sum(axis=1)
+        v1 = dataFlat[vi_flat]
+        v2 = dataFlat[vi_flat + data.strides[i]//data.itemsize]
+        vertexes[vim, i] += (level-v1) / (v2-v1)
+    
+    ### compute the set of vertex indexes for each face. 
+    
+    ## This works, but runs a bit slower.
+    ## all cells with at least one face:
+    #cells = np.argwhere((index != 0) & (index != 255))  
+    #cellInds = index[cells[:, 0], cells[:, 1], cells[:, 2]]
+    #verts = faceTable[cellInds]
+    #mask = verts[..., 0, 0] != 9
+    ## we now have indexes into cut_edges:
+    #verts[...,:3] += cells[:, np.newaxis, np.newaxis,:]
+    #verts = verts[mask]
+    ## and these are the vertex indexes we want:
+    #faces = cut_edges[verts[..., 0], verts[..., 1], verts[..., 2], 
+    #                  verts[..., 3]]  
+    
+    # To allow this to be vectorized efficiently, we count the number of faces 
+    # in each grid cell and handle each group of cells with the same number 
+    # together.
+    
+    # determine how many faces to assign to each grid cell
+    n_faces = n_table_faces[index]
+    tot_faces = n_faces.sum()
+    faces = np.empty((tot_faces, 3), dtype=np.uint32)
+    ptr = 0
+    
+    ## this helps speed up an indexing operation later on
+    cs = np.array(cut_edges.strides)//cut_edges.itemsize
+    cut_edges = cut_edges.flatten()
+
+    ## this, strangely, does not seem to help.
+    #ins = np.array(index.strides)/index.itemsize
+    #index = index.flatten()
+
+    for i in range(1, 6):
+        # expensive:
+        # all cells which require i faces  (argwhere is expensive)
+        cells = np.argwhere(n_faces == i)  
+        if cells.shape[0] == 0:
+            continue
+        # index values of cells to process for this round:
+        cellInds = index[cells[:, 0], cells[:, 1], cells[:, 2]]
+        
+        # expensive:
+        verts = face_shift_tables[i][cellInds]
+        # we now have indexes into cut_edges:
+        verts[..., :3] += cells[:, np.newaxis, np.newaxis, :]
+        verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:])
+        
+        # expensive:
+        verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2)
+        vert_inds = cut_edges[verts]
+        nv = vert_inds.shape[0]
+        faces[ptr:ptr+nv] = vert_inds  # .reshape((nv, 3))
+        ptr += nv
+        
+    return vertexes, faces
+
+
+def _get_data_cache():
+    # Precompute lookup tables on the first run
+    
+    global _data_cache
+    
+    if _data_cache is None:
+        # map from grid cell index to edge index.
+        # grid cell index tells us which corners are below the isosurface,
+        # edge index tells us which edges are cut by the isosurface.
+        # (Data stolen from Bourk; see above.)
+        edge_table = np.array([
+            0x0,   0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
+            0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+            0x190, 0x99,  0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
+            0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+            0x230, 0x339, 0x33,  0x13a, 0x636, 0x73f, 0x435, 0x53c,
+            0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+            0x3a0, 0x2a9, 0x1a3, 0xaa,  0x7a6, 0x6af, 0x5a5, 0x4ac,
+            0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+            0x460, 0x569, 0x663, 0x76a, 0x66,  0x16f, 0x265, 0x36c,
+            0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+            0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff,  0x3f5, 0x2fc,
+            0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+            0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55,  0x15c,
+            0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+            0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, 
+            0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+            0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
+            0xcc,  0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+            0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
+            0x15c, 0x55,  0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+            0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
+            0x2fc, 0x3f5, 0xff,  0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+            0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
+            0x36c, 0x265, 0x16f, 0x66,  0x76a, 0x663, 0x569, 0x460,
+            0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
+            0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa,  0x1a3, 0x2a9, 0x3a0,
+            0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
+            0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33,  0x339, 0x230,
+            0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
+            0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99,  0x190,
+            0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
+            0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0   
+        ], dtype=np.uint16)
+        
+        # Table of triangles to use for filling each grid cell.
+        # Each set of three integers tells us which three edges to
+        # draw a triangle between.
+        # (Data stolen from Bourk; see above.)
+        triTable = [
+            [],
+            [0, 8, 3],
+            [0, 1, 9],
+            [1, 8, 3, 9, 8, 1],
+            [1, 2, 10],
+            [0, 8, 3, 1, 2, 10],
+            [9, 2, 10, 0, 2, 9],
+            [2, 8, 3, 2, 10, 8, 10, 9, 8],
+            [3, 11, 2],
+            [0, 11, 2, 8, 11, 0],
+            [1, 9, 0, 2, 3, 11],
+            [1, 11, 2, 1, 9, 11, 9, 8, 11],
+            [3, 10, 1, 11, 10, 3],
+            [0, 10, 1, 0, 8, 10, 8, 11, 10],
+            [3, 9, 0, 3, 11, 9, 11, 10, 9],
+            [9, 8, 10, 10, 8, 11],
+            [4, 7, 8],
+            [4, 3, 0, 7, 3, 4],
+            [0, 1, 9, 8, 4, 7],
+            [4, 1, 9, 4, 7, 1, 7, 3, 1],
+            [1, 2, 10, 8, 4, 7],
+            [3, 4, 7, 3, 0, 4, 1, 2, 10],
+            [9, 2, 10, 9, 0, 2, 8, 4, 7],
+            [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
+            [8, 4, 7, 3, 11, 2],
+            [11, 4, 7, 11, 2, 4, 2, 0, 4],
+            [9, 0, 1, 8, 4, 7, 2, 3, 11],
+            [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1],
+            [3, 10, 1, 3, 11, 10, 7, 8, 4],
+            [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4],
+            [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
+            [4, 7, 11, 4, 11, 9, 9, 11, 10],
+            [9, 5, 4],
+            [9, 5, 4, 0, 8, 3],
+            [0, 5, 4, 1, 5, 0],
+            [8, 5, 4, 8, 3, 5, 3, 1, 5],
+            [1, 2, 10, 9, 5, 4],
+            [3, 0, 8, 1, 2, 10, 4, 9, 5],
+            [5, 2, 10, 5, 4, 2, 4, 0, 2],
+            [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
+            [9, 5, 4, 2, 3, 11],
+            [0, 11, 2, 0, 8, 11, 4, 9, 5],
+            [0, 5, 4, 0, 1, 5, 2, 3, 11],
+            [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5],
+            [10, 3, 11, 10, 1, 3, 9, 5, 4],
+            [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10],
+            [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
+            [5, 4, 8, 5, 8, 10, 10, 8, 11],
+            [9, 7, 8, 5, 7, 9],
+            [9, 3, 0, 9, 5, 3, 5, 7, 3],
+            [0, 7, 8, 0, 1, 7, 1, 5, 7],
+            [1, 5, 3, 3, 5, 7],
+            [9, 7, 8, 9, 5, 7, 10, 1, 2],
+            [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3],
+            [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
+            [2, 10, 5, 2, 5, 3, 3, 5, 7],
+            [7, 9, 5, 7, 8, 9, 3, 11, 2],
+            [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11],
+            [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
+            [11, 2, 1, 11, 1, 7, 7, 1, 5],
+            [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
+            [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
+            [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0],
+            [11, 10, 5, 7, 11, 5],
+            [10, 6, 5],
+            [0, 8, 3, 5, 10, 6],
+            [9, 0, 1, 5, 10, 6],
+            [1, 8, 3, 1, 9, 8, 5, 10, 6],
+            [1, 6, 5, 2, 6, 1],
+            [1, 6, 5, 1, 2, 6, 3, 0, 8],
+            [9, 6, 5, 9, 0, 6, 0, 2, 6],
+            [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8],
+            [2, 3, 11, 10, 6, 5],
+            [11, 0, 8, 11, 2, 0, 10, 6, 5],
+            [0, 1, 9, 2, 3, 11, 5, 10, 6],
+            [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11],
+            [6, 3, 11, 6, 5, 3, 5, 1, 3],
+            [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6],
+            [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
+            [6, 5, 9, 6, 9, 11, 11, 9, 8],
+            [5, 10, 6, 4, 7, 8],
+            [4, 3, 0, 4, 7, 3, 6, 5, 10],
+            [1, 9, 0, 5, 10, 6, 8, 4, 7],
+            [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
+            [6, 1, 2, 6, 5, 1, 4, 7, 8],
+            [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
+            [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6],
+            [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
+            [3, 11, 2, 7, 8, 4, 10, 6, 5],
+            [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
+            [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
+            [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
+            [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
+            [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
+            [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
+            [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9],
+            [10, 4, 9, 6, 4, 10],
+            [4, 10, 6, 4, 9, 10, 0, 8, 3],
+            [10, 0, 1, 10, 6, 0, 6, 4, 0],
+            [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10],
+            [1, 4, 9, 1, 2, 4, 2, 6, 4],
+            [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4],
+            [0, 2, 4, 4, 2, 6],
+            [8, 3, 2, 8, 2, 4, 4, 2, 6],
+            [10, 4, 9, 10, 6, 4, 11, 2, 3],
+            [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6],
+            [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
+            [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1],
+            [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
+            [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1],
+            [3, 11, 6, 3, 6, 0, 0, 6, 4],
+            [6, 4, 8, 11, 6, 8],
+            [7, 10, 6, 7, 8, 10, 8, 9, 10],
+            [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
+            [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0],
+            [10, 6, 7, 10, 7, 1, 1, 7, 3],
+            [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7],
+            [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
+            [7, 8, 0, 7, 0, 6, 6, 0, 2],
+            [7, 3, 2, 6, 7, 2],
+            [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
+            [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
+            [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11],
+            [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
+            [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6],
+            [0, 9, 1, 11, 6, 7],
+            [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
+            [7, 11, 6],
+            [7, 6, 11],
+            [3, 0, 8, 11, 7, 6],
+            [0, 1, 9, 11, 7, 6],
+            [8, 1, 9, 8, 3, 1, 11, 7, 6],
+            [10, 1, 2, 6, 11, 7],
+            [1, 2, 10, 3, 0, 8, 6, 11, 7],
+            [2, 9, 0, 2, 10, 9, 6, 11, 7],
+            [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8],
+            [7, 2, 3, 6, 2, 7],
+            [7, 0, 8, 7, 6, 0, 6, 2, 0],
+            [2, 7, 6, 2, 3, 7, 0, 1, 9],
+            [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6],
+            [10, 7, 6, 10, 1, 7, 1, 3, 7],
+            [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8],
+            [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
+            [7, 6, 10, 7, 10, 8, 8, 10, 9],
+            [6, 8, 4, 11, 8, 6],
+            [3, 6, 11, 3, 0, 6, 0, 4, 6],
+            [8, 6, 11, 8, 4, 6, 9, 0, 1],
+            [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6],
+            [6, 8, 4, 6, 11, 8, 2, 10, 1],
+            [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6],
+            [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
+            [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3],
+            [8, 2, 3, 8, 4, 2, 4, 6, 2],
+            [0, 4, 2, 4, 6, 2],
+            [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8],
+            [1, 9, 4, 1, 4, 2, 2, 4, 6],
+            [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1],
+            [10, 1, 0, 10, 0, 6, 6, 0, 4],
+            [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3],
+            [10, 9, 4, 6, 10, 4],
+            [4, 9, 5, 7, 6, 11],
+            [0, 8, 3, 4, 9, 5, 11, 7, 6],
+            [5, 0, 1, 5, 4, 0, 7, 6, 11],
+            [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
+            [9, 5, 4, 10, 1, 2, 7, 6, 11],
+            [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
+            [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2],
+            [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
+            [7, 2, 3, 7, 6, 2, 5, 4, 9],
+            [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
+            [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0],
+            [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
+            [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7],
+            [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
+            [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10],
+            [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
+            [6, 9, 5, 6, 11, 9, 11, 8, 9],
+            [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
+            [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11],
+            [6, 11, 3, 6, 3, 5, 5, 3, 1],
+            [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6],
+            [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
+            [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5],
+            [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
+            [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2],
+            [9, 5, 6, 9, 6, 0, 0, 6, 2],
+            [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8],
+            [1, 5, 6, 2, 1, 6],
+            [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6],
+            [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
+            [0, 3, 8, 5, 6, 10],
+            [10, 5, 6],
+            [11, 5, 10, 7, 5, 11],
+            [11, 5, 10, 11, 7, 5, 8, 3, 0],
+            [5, 11, 7, 5, 10, 11, 1, 9, 0],
+            [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
+            [11, 1, 2, 11, 7, 1, 7, 5, 1],
+            [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
+            [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7],
+            [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
+            [2, 5, 10, 2, 3, 5, 3, 7, 5],
+            [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
+            [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2],
+            [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
+            [1, 3, 5, 3, 7, 5],
+            [0, 8, 7, 0, 7, 1, 1, 7, 5],
+            [9, 0, 3, 9, 3, 5, 5, 3, 7],
+            [9, 8, 7, 5, 9, 7],
+            [5, 8, 4, 5, 10, 8, 10, 11, 8],
+            [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
+            [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5],
+            [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
+            [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8],
+            [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
+            [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5],
+            [9, 4, 5, 2, 11, 3],
+            [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4],
+            [5, 10, 2, 5, 2, 4, 4, 2, 0],
+            [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9],
+            [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
+            [8, 4, 5, 8, 5, 3, 3, 5, 1],
+            [0, 4, 5, 1, 0, 5],
+            [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5],
+            [9, 4, 5],
+            [4, 11, 7, 4, 9, 11, 9, 10, 11],
+            [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
+            [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11],
+            [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
+            [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2],
+            [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
+            [11, 7, 4, 11, 4, 2, 2, 4, 0],
+            [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
+            [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9],
+            [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
+            [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10],
+            [1, 10, 2, 8, 7, 4],
+            [4, 9, 1, 4, 1, 7, 7, 1, 3],
+            [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1],
+            [4, 0, 3, 7, 4, 3],
+            [4, 8, 7],
+            [9, 10, 8, 10, 11, 8],
+            [3, 0, 9, 3, 9, 11, 11, 9, 10],
+            [0, 1, 10, 0, 10, 8, 8, 10, 11],
+            [3, 1, 10, 11, 3, 10],
+            [1, 2, 11, 1, 11, 9, 9, 11, 8],
+            [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9],
+            [0, 2, 11, 8, 0, 11],
+            [3, 2, 11],
+            [2, 3, 8, 2, 8, 10, 10, 8, 9],
+            [9, 10, 2, 0, 9, 2],
+            [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
+            [1, 10, 2],
+            [1, 3, 8, 9, 1, 8],
+            [0, 9, 1],
+            [0, 3, 8],
+            []
+        ]
+        
+        # maps edge ID (0-11) to (x, y, z) cell offset and edge ID (0-2)
+        edge_shifts = np.array([
+            [0, 0, 0, 0],   
+            [1, 0, 0, 1],
+            [0, 1, 0, 0],
+            [0, 0, 0, 1],
+            [0, 0, 1, 0],
+            [1, 0, 1, 1],
+            [0, 1, 1, 0],
+            [0, 0, 1, 1],
+            [0, 0, 0, 2],
+            [1, 0, 0, 2],
+            [1, 1, 0, 2],
+            [0, 1, 0, 2],
+            #[9, 9, 9, 9]  ## fake
+            # don't use ubyte here! This value gets added to cell index later; 
+            # will need the extra precision.
+        ], dtype=np.uint16) 
+        n_table_faces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte)
+        face_shift_tables = [None]
+        for i in range(1, 6):
+            ## compute lookup table of index: vertexes mapping
+            faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte)
+            faceTableInds = np.argwhere(n_table_faces == i)
+            faceTableI[faceTableInds[:, 0]] = np.array([triTable[j] for j in 
+                                                        faceTableInds])
+            faceTableI = faceTableI.reshape((len(triTable), i, 3))
+            face_shift_tables.append(edge_shifts[faceTableI])
+            
+        _data_cache = (face_shift_tables, edge_shifts, edge_table, 
+                       n_table_faces)
+        
+    return _data_cache
diff --git a/vispy/geometry/meshdata.py b/vispy/geometry/meshdata.py
new file mode 100644
index 0000000..32a8e7f
--- /dev/null
+++ b/vispy/geometry/meshdata.py
@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+
+from ..ext.six.moves import xrange
+
+
+class MeshData(object):
+    """
+    Class for storing and operating on 3D mesh data.
+
+    Parameters
+    ----------
+    vertices : ndarray, shape (Nv, 3)
+        Vertex coordinates. If faces is not specified, then this will
+        instead be interpreted as (Nf, 3, 3) array of coordinates.
+    faces : ndarray, shape (Nf, 3)
+        Indices into the vertex array.
+    edges : None
+        [not available yet]
+    vertex_colors : ndarray, shape (Nv, 4)
+        Vertex colors. If faces is not specified, this will be
+        interpreted as (Nf, 3, 4) array of colors.
+    face_colors : ndarray, shape (Nf, 4)
+        Face colors.
+
+    Notes
+    -----
+    All arguments are optional.
+
+    The object may contain:
+
+    - list of vertex locations
+    - list of edges
+    - list of triangles
+    - colors per vertex, edge, or tri
+    - normals per vertex or tri
+
+    This class handles conversion between the standard
+    [list of vertices, list of faces] format (suitable for use with
+    glDrawElements) and 'indexed' [list of vertices] format (suitable
+    for use with glDrawArrays). It will automatically compute face normal
+    vectors as well as averaged vertex normal vectors.
+
+    The class attempts to be as efficient as possible in caching conversion
+    results and avoiding unnecessary conversions.
+    """
+
+    def __init__(self, vertices=None, faces=None, edges=None,
+                 vertex_colors=None, face_colors=None):
+        self._vertices = None  # (Nv,3) array of vertex coordinates
+        self._vertices_indexed_by_faces = None  # (Nf, 3, 3) vertex coordinates
+        self._vertices_indexed_by_edges = None  # (Ne, 2, 3) vertex coordinates
+
+        ## mappings between vertices, faces, and edges
+        self._faces = None  # Nx3 indices into self._vertices, 3 verts/face
+        self._edges = None  # Nx2 indices into self._vertices, 2 verts/edge
+        # inverse mappings
+        self._vertex_faces = None  # maps vertex ID to a list of face IDs
+        self._vertex_edges = None  # maps vertex ID to a list of edge IDs
+
+        ## Per-vertex data
+        self._vertex_normals = None                # (Nv, 3) normals
+        self._vertex_normals_indexed_by_faces = None  # (Nf, 3, 3) normals
+        self._vertex_colors = None                 # (Nv, 3) colors
+        self._vertex_colors_indexed_by_faces = None   # (Nf, 3, 4) colors
+        self._vertex_colors_indexed_by_edges = None   # (Nf, 2, 4) colors
+
+        ## Per-face data
+        self._face_normals = None                # (Nf, 3) face normals
+        self._face_normals_indexed_by_faces = None  # (Nf, 3, 3) face normals
+        self._face_colors = None                 # (Nf, 4) face colors
+        self._face_colors_indexed_by_faces = None   # (Nf, 3, 4) face colors
+        self._face_colors_indexed_by_edges = None   # (Ne, 2, 4) face colors
+
+        ## Per-edge data
+        self._edge_colors = None                # (Ne, 4) edge colors
+        self._edge_colors_indexed_by_edges = None  # (Ne, 2, 4) edge colors
+        # default color to use if no face/edge/vertex colors are given
+        #self._meshColor = (1, 1, 1, 0.1)
+
+        if vertices is not None:
+            if faces is None:
+                self.set_vertices(vertices, indexed='faces')
+                if vertex_colors is not None:
+                    self.set_vertex_colors(vertex_colors, indexed='faces')
+                if face_colors is not None:
+                    self.set_face_colors(face_colors, indexed='faces')
+            else:
+                self.set_vertices(vertices)
+                self.set_faces(faces)
+                if vertex_colors is not None:
+                    self.set_vertex_colors(vertex_colors)
+                if face_colors is not None:
+                    self.set_face_colors(face_colors)
+
+    def faces(self):
+        """Array (Nf, 3) of vertex indices, three per triangular face.
+
+        If faces have not been computed for this mesh, returns None.
+        """
+        return self._faces
+
+    def edges(self):
+        """Array (Nf, 3) of vertex indices, two per edge in the mesh."""
+        if self._edges is None:
+            self._compute_edges()
+        return self._edges
+
+    def set_faces(self, faces):
+        """Set the (Nf, 3) array of faces. Each rown in the array contains
+        three indices into the vertex array, specifying the three corners
+        of a triangular face."""
+        self._faces = faces
+        self._edges = None
+        self._vertex_faces = None
+        self._vertices_indexed_by_faces = None
+        self.reset_normals()
+        self._vertex_colors_indexed_by_faces = None
+        self._face_colors_indexed_by_faces = None
+
+    def vertices(self, indexed=None):
+        """Return an array (N,3) of the positions of vertices in the mesh.
+        By default, each unique vertex appears only once in the array.
+        If indexed is 'faces', then the array will instead contain three
+        vertices per face in the mesh (and a single vertex may appear more
+        than once in the array)."""
+        if indexed is None:
+            if (self._vertices is None and
+                    self._vertices_indexed_by_faces is not None):
+                self._compute_unindexed_vertices()
+            return self._vertices
+        elif indexed == 'faces':
+            if (self._vertices_indexed_by_faces is None and
+                    self._vertices is not None):
+                self._vertices_indexed_by_faces = self._vertices[self.faces()]
+            return self._vertices_indexed_by_faces
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def set_vertices(self, verts=None, indexed=None, reset_normals=True):
+        """
+        Set the array (Nv, 3) of vertex coordinates.
+        If indexed=='faces', then the data must have shape (Nf, 3, 3) and is
+        assumed to be already indexed as a list of faces.
+        This will cause any pre-existing normal vectors to be cleared
+        unless reset_normals=False.
+        """
+        if indexed is None:
+            if verts is not None:
+                self._vertices = verts
+            self._vertices_indexed_by_faces = None
+        elif indexed == 'faces':
+            self._vertices = None
+            if verts is not None:
+                self._vertices_indexed_by_faces = verts
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+        if reset_normals:
+            self.reset_normals()
+
+    def reset_normals(self):
+        self._vertex_normals = None
+        self._vertex_normals_indexed_by_faces = None
+        self._face_normals = None
+        self._face_normals_indexed_by_faces = None
+
+    def has_face_indexed_data(self):
+        """Return True if this object already has vertex positions indexed
+        by face"""
+        return self._vertices_indexed_by_faces is not None
+
+    def has_edge_indexed_data(self):
+        return self._vertices_indexed_by_edges is not None
+
+    def has_vertex_color(self):
+        """Return True if this data set has vertex color information"""
+        for v in (self._vertex_colors, self._vertex_colors_indexed_by_faces,
+                  self._vertex_colors_indexed_by_edges):
+            if v is not None:
+                return True
+        return False
+
+    def has_face_color(self):
+        """Return True if this data set has face color information"""
+        for v in (self._face_colors, self._face_colors_indexed_by_faces,
+                  self._face_colors_indexed_by_edges):
+            if v is not None:
+                return True
+        return False
+
+    def face_normals(self, indexed=None):
+        """
+        Return an array (Nf, 3) of normal vectors for each face.
+        If indexed='faces', then instead return an indexed array
+        (Nf, 3, 3)  (this is just the same array with each vector
+        copied three times).
+        """
+        if self._face_normals is None:
+            v = self.vertices(indexed='faces')
+            self._face_normals = np.cross(v[:, 1] - v[:, 0],
+                                          v[:, 2] - v[:, 0])
+
+        if indexed is None:
+            return self._face_normals
+        elif indexed == 'faces':
+            if self._face_normals_indexed_by_faces is None:
+                norms = np.empty((self._face_normals.shape[0], 3, 3),
+                                 dtype=np.float32)
+                norms[:] = self._face_normals[:, np.newaxis, :]
+                self._face_normals_indexed_by_faces = norms
+            return self._face_normals_indexed_by_faces
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def vertex_normals(self, indexed=None):
+        """
+        Return an array of normal vectors.
+        By default, the array will be (N, 3) with one entry per unique
+        vertex in the mesh. If indexed is 'faces', then the array will
+        contain three normal vectors per face (and some vertices may be
+        repeated).
+        """
+        if self._vertex_normals is None:
+            faceNorms = self.face_normals()
+            vertFaces = self.vertex_faces()
+            self._vertex_normals = np.empty(self._vertices.shape,
+                                            dtype=np.float32)
+            for vindex in xrange(self._vertices.shape[0]):
+                faces = vertFaces[vindex]
+                if len(faces) == 0:
+                    self._vertex_normals[vindex] = (0, 0, 0)
+                    continue
+                norms = faceNorms[faces]  # get all face normals
+                norm = norms.sum(axis=0)  # sum normals
+                norm /= (norm**2).sum()**0.5  # and re-normalize
+                self._vertex_normals[vindex] = norm
+
+        if indexed is None:
+            return self._vertex_normals
+        elif indexed == 'faces':
+            return self._vertex_normals[self.faces()]
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def vertex_colors(self, indexed=None):
+        """
+        Return an array (Nv, 4) of vertex colors.
+        If indexed=='faces', then instead return an indexed array
+        (Nf, 3, 4).
+        """
+        if indexed is None:
+            return self._vertex_colors
+        elif indexed == 'faces':
+            if self._vertex_colors_indexed_by_faces is None:
+                self._vertex_colors_indexed_by_faces = \
+                    self._vertex_colors[self.faces()]
+            return self._vertex_colors_indexed_by_faces
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def set_vertex_colors(self, colors, indexed=None):
+        """
+        Set the vertex color array (Nv, 4).
+        If indexed=='faces', then the array will be interpreted
+        as indexed and should have shape (Nf, 3, 4)
+        """
+        if indexed is None:
+            self._vertex_colors = colors
+            self._vertex_colors_indexed_by_faces = None
+        elif indexed == 'faces':
+            self._vertex_colors = None
+            self._vertex_colors_indexed_by_faces = colors
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def face_colors(self, indexed=None):
+        """
+        Return an array (Nf, 4) of face colors.
+        If indexed=='faces', then instead return an indexed array
+        (Nf, 3, 4)  (note this is just the same array with each color
+        repeated three times).
+        """
+        if indexed is None:
+            return self._face_colors
+        elif indexed == 'faces':
+            if (self._face_colors_indexed_by_faces is None and
+                    self._face_colors is not None):
+                Nf = self._face_colors.shape[0]
+                self._face_colors_indexed_by_faces = \
+                    np.empty((Nf, 3, 4), dtype=self._face_colors.dtype)
+                self._face_colors_indexed_by_faces[:] = \
+                    self._face_colors.reshape(Nf, 1, 4)
+            return self._face_colors_indexed_by_faces
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def set_face_colors(self, colors, indexed=None):
+        """
+        Set the face color array (Nf, 4).
+        If indexed=='faces', then the array will be interpreted
+        as indexed and should have shape (Nf, 3, 4)
+        """
+        if indexed is None:
+            self._face_colors = colors
+            self._face_colors_indexed_by_faces = None
+        elif indexed == 'faces':
+            self._face_colors = None
+            self._face_colors_indexed_by_faces = colors
+        else:
+            raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
+
+    def face_count(self):
+        """
+        Return the number of faces in the mesh.
+        """
+        if self._faces is not None:
+            return self._faces.shape[0]
+        elif self._vertices_indexed_by_faces is not None:
+            return self._vertices_indexed_by_faces.shape[0]
+
+    def edge_colors(self):
+        return self._edge_colors
+
+    #def _set_indexed_faces(self, faces, vertex_colors=None, face_colors=None):
+        #self._vertices_indexed_by_faces = faces
+        #self._vertex_colors_indexed_by_faces = vertex_colors
+        #self._face_colors_indexed_by_faces = face_colors
+
+    def _compute_unindexed_vertices(self):
+        ## Given (Nv, 3, 3) array of vertices-indexed-by-face, convert
+        ## backward to unindexed vertices
+        ## This is done by collapsing into a list of 'unique' vertices
+        ## (difference < 1e-14)
+
+        ## I think generally this should be discouraged..
+        faces = self._vertices_indexed_by_faces
+        verts = {}  # used to remember the index of each vertex position
+        self._faces = np.empty(faces.shape[:2], dtype=np.uint)
+        self._vertices = []
+        self._vertex_faces = []
+        self._face_normals = None
+        self._vertex_normals = None
+        for i in xrange(faces.shape[0]):
+            face = faces[i]
+            for j in range(face.shape[0]):
+                pt = face[j]
+                # quantize to ensure nearly-identical points will be merged
+                pt2 = tuple([round(x*1e14) for x in pt])
+                index = verts.get(pt2, None)
+                if index is None:
+                    #self._vertices.append(QtGui.QVector3D(*pt))
+                    self._vertices.append(pt)
+                    self._vertex_faces.append([])
+                    index = len(self._vertices)-1
+                    verts[pt2] = index
+                # track which vertices belong to which faces
+                self._vertex_faces[index].append(i)
+                self._faces[i, j] = index
+        self._vertices = np.array(self._vertices, dtype=np.float32)
+
+    #def _setUnindexedFaces(self, faces, vertices, vertex_colors=None,
+        #                   face_colors=None):
+        #self._vertices = vertices #[QtGui.QVector3D(*v) for v in vertices]
+        #self._faces = faces.astype(np.uint)
+        #self._edges = None
+        #self._vertex_faces = None
+        #self._face_normals = None
+        #self._vertex_normals = None
+        #self._vertex_colors = vertex_colors
+        #self._face_colors = face_colors
+
+    def vertex_faces(self):
+        """
+        List mapping each vertex index to a list of face indices that use it.
+        """
+        if self._vertex_faces is None:
+            self._vertex_faces = [[] for i in xrange(len(self.vertices()))]
+            for i in xrange(self._faces.shape[0]):
+                face = self._faces[i]
+                for ind in face:
+                    self._vertex_faces[ind].append(i)
+        return self._vertex_faces
+
+    #def reverseNormals(self):
+        #"""
+        #Reverses the direction of all normal vectors.
+        #"""
+        #pass
+
+    #def generateEdgesFromFaces(self):
+        #"""
+        #Generate a set of edges by listing all the edges of faces and
+        #removing any duplicates.
+        #Useful for displaying wireframe meshes.
+        #"""
+        #pass
+
+    def _compute_edges(self):
+        if not self.has_face_indexed_data:
+            ## generate self._edges from self._faces
+            nf = len(self._faces)
+            edges = np.empty(nf*3, dtype=[('i', np.uint, 2)])
+            edges['i'][0:nf] = self._faces[:, :2]
+            edges['i'][nf:2*nf] = self._faces[:, 1:3]
+            edges['i'][-nf:, 0] = self._faces[:, 2]
+            edges['i'][-nf:, 1] = self._faces[:, 0]
+
+            # sort per-edge
+            mask = edges['i'][:, 0] > edges['i'][:, 1]
+            edges['i'][mask] = edges['i'][mask][:, ::-1]
+
+            # remove duplicate entries
+            self._edges = np.unique(edges)['i']
+        elif self._vertices_indexed_by_faces is not None:
+            verts = self._vertices_indexed_by_faces
+            edges = np.empty((verts.shape[0], 3, 2), dtype=np.uint)
+            nf = verts.shape[0]
+            edges[:, 0, 0] = np.arange(nf) * 3
+            edges[:, 0, 1] = edges[:, 0, 0] + 1
+            edges[:, 1, 0] = edges[:, 0, 1]
+            edges[:, 1, 1] = edges[:, 1, 0] + 1
+            edges[:, 2, 0] = edges[:, 1, 1]
+            edges[:, 2, 1] = edges[:, 0, 0]
+            self._edges = edges
+        else:
+            raise Exception("MeshData cannot generate edges--no faces in "
+                            "this data.")
+
+    def save(self):
+        """Serialize this mesh to a string appropriate for disk storage"""
+        import pickle
+        if self._faces is not None:
+            names = ['_vertices', '_faces']
+        else:
+            names = ['_vertices_indexed_by_faces']
+
+        if self._vertex_colors is not None:
+            names.append('_vertex_colors')
+        elif self._vertex_colors_indexed_by_faces is not None:
+            names.append('_vertex_colors_indexed_by_faces')
+
+        if self._face_colors is not None:
+            names.append('_face_colors')
+        elif self._face_colors_indexed_by_faces is not None:
+            names.append('_face_colors_indexed_by_faces')
+
+        state = dict([(n, getattr(self, n)) for n in names])
+        return pickle.dumps(state)
+
+    def restore(self, state):
+        """Restore the state of a mesh previously saved using save()"""
+        import pickle
+        state = pickle.loads(state)
+        for k in state:
+            if isinstance(state[k], list):
+                state[k] = np.array(state[k])
+            setattr(self, k, state[k])
diff --git a/vispy/geometry/polygon.py b/vispy/geometry/polygon.py
new file mode 100644
index 0000000..c43ff4a
--- /dev/null
+++ b/vispy/geometry/polygon.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+import numpy as np
+
+from .triangulation import Triangulation
+
+
+class PolygonData(object):
+    """Polygon class for data handling
+
+    Parameters
+    ----------
+    vertices : (Nv, 3) array
+        Vertex coordinates. If faces is not specified, then this will instead
+        be interpreted as (Nf, 3, 3) array of coordinates.
+    faces : (Nf, 3) array
+        Indexes into the vertex array.
+    edges : (Nv, 2) array
+        Constraining edges specified by vertex indices.
+
+    Notes
+    -----
+    All arguments are optional.
+    """
+    def __init__(self, vertices=None, edges=None, faces=None):
+        self._vertices = vertices
+        self._edges = edges
+        self._faces = faces
+        self._convex_hull = None
+
+    @property
+    def faces(self):
+        """Return an array (Nf, 3) of vertex indexes, three per triangular
+        face in the mesh.
+
+        If faces have not been computed for this mesh, the function
+        computes them.
+        If no vertices or faces are specified, the function returns None.
+        """
+
+        if self._faces is None:
+            if self._vertices is None:
+                return None
+            self.triangulate()
+        return self._faces
+
+    @faces.setter
+    def faces(self, f):
+        """
+        If vertices and faces are incompatible, this will generate vertices
+        from these faces and set them.
+        """
+        self._faces = f
+
+    @property
+    def vertices(self):
+        """Return an array (Nf, 3) of vertices.
+
+        If only faces exist, the function computes the vertices and
+        returns them.
+        If no vertices or faces are specified, the function returns None.
+        """
+
+        if self._faces is None:
+            if self._vertices is None:
+                return None
+            self.triangulate()
+        return self._vertices
+
+    @vertices.setter
+    def vertices(self, v):
+        """
+        If vertices and faces are incompatible, this will generate faces
+        from these vertices and set them.
+        """
+        self._vertices = v
+
+    @property
+    def edges(self):
+        """Return an array (Nv, 2) of vertex indices.
+
+        If no vertices or faces are specified, the function returns None.
+        """
+        return self._edges
+
+    @edges.setter
+    def edges(self, e):
+        """
+        Ensures that all edges are valid.
+        """
+        self._edges = e
+
+    @property
+    def convex_hull(self):
+        """Return an array of vertex indexes representing the convex hull.
+
+        If faces have not been computed for this mesh, the function
+        computes them.
+        If no vertices or faces are specified, the function returns None.
+        """
+        if self._faces is None:
+            if self._vertices is None:
+                return None
+            self.triangulate()
+        return self._convex_hull
+
+    def triangulate(self):
+        """
+        Triangulates the set of vertices and stores the triangles in faces and
+        the convex hull in convex_hull.
+        """
+        npts = self._vertices.shape[0]
+        if np.any(self._vertices[0] != self._vertices[1]):
+            # start != end, so edges must wrap around to beginning.
+            edges = np.empty((npts, 2), dtype=np.uint)
+            edges[:, 0] = np.arange(npts)
+            edges[:, 1] = edges[:, 0] + 1
+            edges[-1, 1] = 0
+        else:
+            # start == end; no wrapping required.
+            edges = np.empty((npts-1, 2), dtype=np.uint)
+            edges[:, 0] = np.arange(npts)
+            edges[:, 1] = edges[:, 0] + 1
+
+        tri = Triangulation(self._vertices, edges)
+        tri.triangulate()
+        return tri.pts, tri.tris
+
+    def add_vertex(self, vertex):
+        """
+        Adds given vertex and retriangulates to generate new faces.
+        """
+        pass
diff --git a/vispy/geometry/rect.py b/vispy/geometry/rect.py
new file mode 100644
index 0000000..e41aa44
--- /dev/null
+++ b/vispy/geometry/rect.py
@@ -0,0 +1,157 @@
+import numpy as np
+
+
+class Rect(object):
+    """
+    Representation of a rectangular area in a 2D coordinate system.
+
+    Parameters
+    ----------
+    *args : arguments
+        Can be in the form `Rect(x, y, w, h)`, `Rect(pos, size)`, or
+        `Rect(Rect)`.
+    """
+    def __init__(self, *args, **kwds):
+        self._pos = (0, 0)
+        self._size = (0, 0)
+
+        if len(args) == 1 and isinstance(args[0], Rect):
+            self._pos = args[0]._pos
+            self._size = args[0]._size
+        elif len(args) == 2:
+            self._pos = tuple(args[0])
+            self._size = tuple(args[1])
+        elif len(args) == 4:
+            self._pos = tuple(args[:2])
+            self._size = tuple(args[2:])
+        elif len(args) != 0:
+            raise TypeError("Rect must be instantiated with 0, 1, 2, or 4 "
+                            "non-keyword arguments.")
+
+        self._pos = kwds.get('pos', self._pos)
+        self._size = kwds.get('size', self._size)
+
+        if len(self._pos) != 2 or len(self._size) != 2:
+            raise ValueError("Rect pos and size arguments must have 2 "
+                             "elements.")
+
+    @property
+    def pos(self):
+        return self._pos
+
+    @pos.setter
+    def pos(self, p):
+        assert len(p) == 2
+        self._pos = p
+
+    @property
+    def size(self):
+        return self._size
+
+    @size.setter
+    def size(self, s):
+        assert len(s) == 2
+        self._size = s
+
+    @property
+    def width(self):
+        return self.size[0]
+
+    @width.setter
+    def width(self, w):
+        self.size[0] = w
+
+    @property
+    def height(self):
+        return self.size[1]
+
+    @height.setter
+    def height(self, h):
+        self.size[1] = h
+
+    @property
+    def left(self):
+        return self.pos[0]
+
+    @left.setter
+    def left(self, x):
+        self.size = (self.size[0] + (self.pos[0] - x), self.size[1])
+        self.pos = (x, self.pos[1])
+
+    @property
+    def right(self):
+        return self.pos[0] + self.size[0]
+
+    @right.setter
+    def right(self, x):
+        self.size = (x - self.pos[0], self.size[1])
+
+    @property
+    def bottom(self):
+        return self.pos[1]
+
+    @bottom.setter
+    def bottom(self, y):
+        self.size = (self.size[0], self.size[1] + (self.pos[1] - y))
+        self.pos = (self.pos[0], y)
+
+    @property
+    def top(self):
+        return self.pos[1] + self.size[1]
+
+    @top.setter
+    def top(self, y):
+        self.size = (self.size[0], y - self.pos[1])
+
+    def padded(self, padding):
+        """Return a new Rect padded (smaller) by *padding* on all sides."""
+        return Rect(pos=(self.pos[0]+padding, self.pos[1]+padding),
+                    size=(self.size[0]-2*padding, self.size[1]-2*padding))
+
+    def normalized(self):
+        """Return a Rect covering the same area, but with height and width
+        guaranteed to be positive."""
+        return Rect(pos=(min(self.left, self.right),
+                         min(self.top, self.bottom)),
+                    size=(abs(self.width), abs(self.height)))
+
+    def flipped(self, x=False, y=True):
+        """ Return a Rect with the same bounds, but with the x or y axes 
+        inverted.
+        """
+        pos = list(self.pos)
+        size = list(self.size)
+        for i, flip in enumerate((x, y)):
+            if flip:
+                pos[i] += size[i]
+                size[i] *= -1
+        return Rect(pos, size)
+
+    def __eq__(self, r):
+        if not isinstance(r, Rect):
+            return False
+        return (np.all(np.equal(r.pos, self.pos)) and
+                np.all(np.equal(r.size, self.size)))
+
+    def __add__(self, a):
+        """ Return this Rect translated by *a*.
+        """
+        return self._transform_out(self._transform_in()[:, :2] + a[:2])
+
+    def contains(self, x, y):
+        return (x >= self.left and x <= self.right and
+                y >= self.bottom and y <= self.top)
+
+    def __repr__(self):
+        return "<Rect (%g, %g) (%g, %g)>" % (self.pos + self.size)
+
+    def _transform_in(self):
+        """Return array of coordinates that can be mapped by Transform
+        classes."""
+        return np.array([
+            [self.left, self.bottom, 0, 1],
+            [self.right, self.top, 0, 1]])
+
+    def _transform_out(self, coords):
+        """Return a new Rect from coordinates mapped after _transform_in()."""
+        return Rect(pos=coords[0, :2], size=coords[1, :2]-coords[0, :2])
diff --git a/vispy/shaders/__init__.py b/vispy/geometry/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/geometry/tests/__init__.py
diff --git a/vispy/geometry/tests/test_generation.py b/vispy/geometry/tests/test_generation.py
new file mode 100644
index 0000000..f28df9d
--- /dev/null
+++ b/vispy/geometry/tests/test_generation.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+import numpy as np
+from numpy.testing import assert_array_equal, assert_allclose
+
+from vispy.geometry import create_cube, create_cylinder, create_sphere
+
+
+def test_cube():
+    """Test cube function"""
+    vertices, filled, outline = create_cube()
+    assert_array_equal(np.arange(len(vertices)), np.unique(filled))
+    assert_array_equal(np.arange(len(vertices)), np.unique(outline))
+
+
+def test_sphere():
+    """Test sphere function"""
+    md = create_sphere(10, 20, radius=10)
+    radii = np.sqrt((md.vertices() ** 2).sum(axis=1))
+    assert_allclose(radii, np.ones_like(radii) * 10)
+
+
+def test_cylinder():
+    """Test cylinder function"""
+    md = create_cylinder(10, 20, radius=[10, 10])
+    radii = np.sqrt((md.vertices()[:, :2] ** 2).sum(axis=1))
+    assert_allclose(radii, np.ones_like(radii) * 10)
diff --git a/vispy/geometry/tests/test_triangulation.py b/vispy/geometry/tests/test_triangulation.py
new file mode 100644
index 0000000..24cc50f
--- /dev/null
+++ b/vispy/geometry/tests/test_triangulation.py
@@ -0,0 +1,508 @@
+import numpy as np
+from numpy.testing import assert_array_almost_equal
+
+from vispy.geometry.triangulation import Triangulation as T
+
+
+def assert_array_eq(a, b):
+    assert a.shape == b.shape
+    assert a.dtype == b.dtype
+    mask = np.isnan(a)
+    assert np.all(np.isnan(b[mask]))
+    assert np.all(a[~mask] == b[~mask])
+
+
+def test_intersect_edge_arrays():
+    global t
+    pts = np.array([
+        [0., 0.],
+        [0., 10.],
+        [5., 0.],
+        [-5., 0.],
+        [-1., 11.],
+        [1., 9.],
+    ])
+    edges = np.array([
+        [0, 1],
+        [2, 3],
+        [0, 3],
+        [4, 5],
+        [4, 1],
+        [0, 1],
+    ])
+    
+    lines = pts[edges]
+    t = T(pts, edges)
+    
+    # intersect array of one edge with a array of many edges
+    intercepts = t.intersect_edge_arrays(lines[0:1], lines[1:])
+    expect = np.array([0.5, 0.0, 0.5, 1.0, np.nan])
+    assert_array_eq(intercepts, expect)
+
+    # intersect every line with every line
+    intercepts = t.intersect_edge_arrays(lines[:, np.newaxis, ...], 
+                                         lines[np.newaxis, ...])
+    for i in range(lines.shape[0]):
+        int2 = t.intersect_edge_arrays(lines[i], lines)
+        assert_array_eq(intercepts[i], int2)
+
+
+def test_edge_intersections():
+    global t
+    pts = np.array([
+        [0, 0],
+        [1, 0],
+        [1, 1],
+        [0, 1],
+        [0, 0.5],  # three edges intersect here
+        [2, 0.5],
+        [-1, 0.2],
+        [2, 0.8],
+        [-1, 1],
+        [0, 0.5],
+    ])
+    edges = np.array([
+        [0, 1],
+        [1, 2],
+        [2, 3],
+        [3, 0],
+        [4, 5],
+        [6, 7],
+        [8, 9],
+    ])
+    
+    t = T(pts, edges)
+    
+    # first test find_edge_intersections
+    cuts = t.find_edge_intersections()
+    expect = {
+        0: [],
+        1: [(0.5, [1., 0.5]), 
+            (0.6, [1., 0.6])],
+        2: [],
+        3: [(0.5, [0., 0.5]), 
+            (0.6, [0., 0.4])],
+        4: [(0.25, [0.5, 0.5]), 
+            (0.5, [1., 0.5])],
+        5: [(1./3., [0., 0.4]), 
+            (0.5, [0.5, 0.5]), 
+            (2./3., [1., 0.6])],
+    }
+        
+    assert len(expect) == len(cuts)
+    for k, v in expect.items():
+        assert len(v) == len(cuts[k])
+        for i, ecut in enumerate(v):
+            vcut = cuts[k][i]
+            assert len(vcut) == len(ecut)
+            for j in range(len(vcut)):
+                assert_array_almost_equal(np.array(ecut[j]), np.array(vcut[j]))
+                
+    # next test that we can split the edges correctly
+    t.split_intersecting_edges()
+    pts = np.array([[0., 0.],
+                    [1., 0.],
+                    [1., 1.],
+                    [0., 1.],
+                    [0., 0.5],
+                    [2., 0.5],
+                    [-1., 0.2],
+                    [2., 0.8],
+                    [-1., 1.],
+                    [0., 0.5],
+                    [1., 0.5],
+                    [1., 0.6],
+                    [0., 0.5],
+                    [0., 0.4],
+                    [0.5, 0.5],
+                    [1., 0.5],
+                    [0., 0.4],
+                    [0.5, 0.5],
+                    [1., 0.6]])
+    edges = np.array([[0, 1],
+                      [1, 10],
+                      [2, 3],
+                      [3, 12],
+                      [4, 14],
+                      [6, 16],
+                      [8, 9],
+                      [10, 11],
+                      [11, 2],
+                      [12, 13],
+                      [13, 0],
+                      [14, 15],
+                      [15, 5],
+                      [16, 17],
+                      [17, 18],
+                      [18, 7]])
+    
+    assert_array_almost_equal(pts, t.pts)
+    assert np.all(edges == t.edges)
+
+    # Test _nearly_ parallel lines. 
+    pts = np.array([[0., 0.],
+                    [1.62434542,  0.],
+                    [1.62434542, -0.61175638],
+                    [1.09617364, -0.61175638]])
+    
+    edges = np.array([[0, 1], [1, 2], [2, 3], [3, 0]])
+    t = T(pts, edges)
+    for edge, cuts in t.find_edge_intersections().items():
+        assert len(cuts) == 0
+
+    
+def test_merge_duplicate_points():
+    global t
+    pts = np.array([
+        [0, 0],
+        [1, 1], 
+        [0.1, 0.7],
+        [2, 3],
+        [0, 0],
+        [0.1, 0.7], 
+        [5, 6],
+    ])
+    edges = np.array([
+        [0, 6],
+        [1, 5],
+        [2, 4],
+        [3, 6],
+        [4, 5],
+    ])
+    
+    t = T(pts, edges)
+    t.merge_duplicate_points()
+
+    pts = np.array([
+        [0, 0],
+        [1, 1], 
+        [0.1, 0.7],
+        [2, 3],
+        [5, 6],
+    ])
+    edges = np.array([
+        [0, 4],
+        [1, 2],
+        [2, 0],
+        [3, 4],
+        [0, 2],
+    ])
+    assert np.allclose(t.pts, pts)
+    assert np.all(t.edges == edges)
+
+
+def test_initialize():
+    # check points are correctly sorted
+    # check artificial points are outside bounds of all others
+    # check tops / bottoms
+    pass
+
+
+def test_utility_methods():
+    global t
+    pts = np.array([
+        [0, 0],
+        [1, 0],
+        [2, 0], 
+        [3, 0],
+        [1.5, 2],
+        [1.5, -2],
+    ])
+    edges = np.array([
+        [4, 5],  # edge cuts through triangle (1, 2, 4) 
+    ])
+
+    t = T(pts, edges)
+    # skip initialization and just simulate being part-way through 
+    # triangulation
+    for tri in [[0, 1, 4], [1, 2, 4], [2, 3, 4]]:
+        t.add_tri(*tri)
+    
+    # find_cut_triangle
+    assert t.find_cut_triangle((4, 5)) == (4, 1, 2)
+    
+    # orientation
+    assert t.orientation((4, 5), 0) == 1
+    assert t.orientation((4, 5), 1) == 1
+    assert t.orientation((4, 5), 2) == -1
+    assert t.orientation((4, 5), 3) == -1
+    assert t.orientation((4, 5), 4) == 0
+    assert t.orientation((4, 5), 5) == 0
+    
+    # distance
+    dist = ((t.pts[0]-t.pts[1])**2).sum()**0.5
+    assert t.distance(t.pts[0], t.pts[1]) == dist
+
+    # adjacent_tri
+    assert t.adjacent_tri((1, 4), 0) == (4, 1, 2)
+    assert t.adjacent_tri((0, 4), 1) is None
+    assert t.adjacent_tri((1, 4), (1, 4, 0)) == (4, 1, 2)
+    assert t.adjacent_tri((0, 4), (1, 4, 0)) is None
+    try:
+        t.adjacent_tri((1, 4), 5)
+    except RuntimeError:
+        pass
+    else:
+        raise Exception("Expected RuntimeError.")
+
+    # edges_intersect
+    assert not t.edges_intersect((0, 1), (1, 2))
+    assert not t.edges_intersect((0, 2), (1, 2))
+    assert t.edges_intersect((4, 5), (1, 2))
+
+    # is_constraining_edge
+    assert t.is_constraining_edge((4, 5))
+    assert t.is_constraining_edge((5, 4))
+    assert not t.is_constraining_edge((3, 5))
+    assert not t.is_constraining_edge((3, 2))
+
+
+def test_projection():
+    pts = np.array([[0, 0],
+                    [5, 0],
+                    [1, 2],
+                    [3, 4]])
+    t = T(pts, np.zeros((0, 2)))
+    
+    a, b, c, d = pts
+    assert np.allclose(t.projection(a, c, b), [1, 0]) 
+    assert np.allclose(t.projection(b, c, a), [1, 0]) 
+    assert np.allclose(t.projection(a, d, b), [3, 0]) 
+    assert np.allclose(t.projection(b, d, a), [3, 0]) 
+    assert np.allclose(t.projection(a, b, c), [1, 2]) 
+    assert np.allclose(t.projection(c, b, a), [1, 2]) 
+
+    
+def test_random(): 
+    # Just test that these triangulate without exception.
+    # TODO: later on, we can turn this same test into an image comparison
+    # with Polygon.
+    N = 10
+    np.random.seed(0)
+    
+    for i in range(4):
+        pts = np.random.normal(size=(N, 2))
+        edges = np.zeros((N, 2), dtype=int)
+        edges[:, 0] = np.arange(N)
+        edges[:, 1] = np.arange(1, N+1) % N
+    
+        t = T(pts, edges)
+        t.triangulate()
+    
+    theta = np.linspace(0, 2*np.pi, 11)[:-1]
+    pts = np.hstack([np.cos(theta)[:, np.newaxis], 
+                    np.sin(theta)[:, np.newaxis]])
+    pts[::2] *= 0.4
+    edges = np.empty((pts.shape[0], 2), dtype=np.uint)
+    edges[:, 0] = np.arange(pts.shape[0])
+    edges[:, 1] = edges[:, 0] + 1
+    edges[-1, 1] = 0
+    t = T(pts, edges)
+    t.triangulate()
+    
+    # much larger test
+    # this should pass, but takes forever..
+    #N = 4000
+    #pts = np.random.normal(size=(N, 2))
+    #pts = np.cumsum(pts, axis=0)
+    #edges = np.zeros((N, 2), dtype=int)
+    #edges[:,0] = np.arange(N)
+    #edges[:,1] = np.arange(1,N+1) % N
+    
+    #t = T(pts, edges)
+    #t.triangulate()
+
+
+def test_orthogonal():
+    # make lines that are entirely vertical / horizontal
+    np.random.seed(1)
+    N = 100
+    pts = [[0, 0]]
+    for i in range(N - 1):
+        p = pts[-1][:]
+        p[i % 2] += np.random.normal()
+        pts.append(p)
+    pts = np.array(pts)
+    edges = np.zeros((N, 2), dtype=int)
+    edges[:, 0] = np.arange(N)
+    edges[:, 1] = np.arange(1, N + 1) % N
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    
+def test_edge_event():
+    # mode 1
+    pts = np.array([[0, 0],
+                    [5, -10],
+                    [10, 0],
+                    [6, -5],
+                    [5, 5],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+    
+    # mode 2
+    pts = np.array([[0, 0],
+                    [10, 0],
+                    [20, 0],
+                    [5, 11],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+    
+    # mode 1, 2
+    pts = np.array([[0, 0],
+                    [10, 0],
+                    [20, 0],
+                    [5, 11],
+                    [9, 10],
+                    [0, 20],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+
+    # mode 2, 1
+    pts = np.array([[0, 0],
+                    [10, 0],
+                    [20, 0],
+                    [15, 8],
+                    [15, 1],
+                    [-5, 10],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+    
+    # mode 2, 1 with many triangles
+    pts = np.array([[0, 10],
+                    [2, 8],
+                    [4, 6],
+                    [6, 4],
+                    [8, 2],
+                    [10, 0],
+                    
+                    [20, 5],
+                    [20, 20],
+                    
+                    [2, 13],
+                    [4, 11],
+                    [6, 9],
+                    [8, 7],
+                    [10, 5],
+                    
+                    [10, 1],
+                    [0, 15],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+
+    # mode 1, 2, 1, 2, 1
+    pts = np.array([[0, 10],
+                    [2, 9],
+                    [4, 8],
+                    [6, 7],
+                    [8, 6],
+                    [10, 5],
+                    
+                    [20, 5],
+                    [20, 20],
+                    
+                    [2, 11],
+                    [19, 19],
+                    [6, 9],
+                    [19, 18],
+                    [10, 7],
+                    
+                    [11, 5.1],
+                    [0, 11.1],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+
+    # mode 2, 1, 2, 1
+    pts = np.array([[0, 10],
+                    [2, 9],
+                    [4, 8],
+                    [6, 7],
+                    [8, 6],
+                    [10, 5],
+                    
+                    [20, 5],
+                    [20, 20],
+                    
+                    [6, 9],
+                    [19, 18],
+                    [10, 7],
+                    
+                    [11, 5.1],
+                    [0, 11.1],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+    
+    # 1, 2  upper/lower polygon order check
+    pts = np.array([[-5, 0],
+                    [-3, 0],
+                    [10, 0],
+                    [15, 15],
+                    [4, 9],
+                    [6, 8.8],
+                    [9, 10],
+                    ])
+    inds = np.arange(pts.shape[0])[:, np.newaxis]
+    edges = np.hstack([inds, np.roll(inds, -1)])
+    
+    t = T(pts, edges)
+    t.triangulate()
+
+    t = T(pts * [-1, 1], edges)
+    t.triangulate()
+    
+
+if __name__ == '__main__':
+    #test_edge_intersections()
+    #test_merge_duplicate_points()
+    #test_utility_methods()
+    test_intersect_edge_arrays()
diff --git a/vispy/geometry/triangulation.py b/vispy/geometry/triangulation.py
new file mode 100644
index 0000000..e5d4d13
--- /dev/null
+++ b/vispy/geometry/triangulation.py
@@ -0,0 +1,965 @@
+# -*- coding: utf8 -*-
+from __future__ import division, print_function
+
+import numpy as np
+from ..ext.ordereddict import OrderedDict
+from itertools import permutations
+
+
+class Triangulation(object):
+    """Constrained delaunay triangulation
+
+    Implementation based on:
+
+        * Domiter, V. and Žalik, B. Sweep‐line algorithm for constrained
+          Delaunay triangulation
+
+    Parameters
+    ----------
+    pts : array
+        Nx2 array of points.
+    edges : array
+        Nx2 array of edges (dtype=int).
+
+    Notes
+    -----
+    * Delaunay legalization is not yet implemented. This produces a proper
+      triangulation, but adding legalisation would produce fewer thin
+      triangles.
+    * The pts and edges arrays may be modified.
+    """
+    def __init__(self, pts, edges):
+        self.pts = pts[:, :2].astype(np.float32)
+        self.edges = edges
+        if self.pts.ndim != 2 or self.pts.shape[1] != 2:
+            raise TypeError('pts argument must be ndarray of shape (N, 2).')
+        if self.edges.ndim != 2 or self.edges.shape[1] != 2:
+            raise TypeError('edges argument must be ndarray of shape (N, 2).')
+        
+        # described in initialize()
+        self.front = None
+        self.tris = OrderedDict()
+        self.edges_lookup = {}
+        
+    def normalize(self):
+        # Clean up data   (not discussed in original publication)
+        
+        # (i) Split intersecting edges. Every edge that intersects another 
+        #     edge or point is split. This extends self.pts and self.edges.
+        self.split_intersecting_edges()
+        
+        # (ii) Merge identical points. If any two points are found to be equal,
+        #      the second is removed and the edge table is updated accordingly. 
+        self.merge_duplicate_points()
+
+        # (iii) Remove duplicate edges
+        # TODO
+
+    def initialize(self):
+        self.normalize()
+        ## Initialization (sec. 3.3)
+
+        # sort points by y, then x
+        flat_shape = self.pts.shape[0] * self.pts.shape[1]
+        pts = self.pts.reshape(flat_shape).view([('x', np.float32), 
+                                                 ('y', np.float32)])
+        order = np.argsort(pts, order=('y', 'x'))
+        pts = pts[order]
+        # update edges to match new point order
+        invorder = np.argsort(order)
+        self.edges = invorder[self.edges]
+        self.pts = pts.view(np.float32).reshape(len(pts), 2)
+
+        # make artificial points P-1 and P-2
+        xmax = self.pts[:, 0].max()
+        xmin = self.pts[:, 0].min()
+        ymax = self.pts[:, 1].max()
+        ymin = self.pts[:, 1].min()
+        xa = (xmax-xmin) * 0.3
+        ya = (ymax-ymin) * 0.3
+        p1 = (xmin - xa, ymin - ya)
+        p2 = (xmax + xa, ymin - ya)
+
+        # prepend artificial points to point list
+        newpts = np.empty((self.pts.shape[0]+2, 2), dtype=float)
+        newpts[0] = p1
+        newpts[1] = p2
+        newpts[2:] = self.pts
+        self.pts = newpts
+        self.edges += 2
+
+        # find topmost point in each edge
+        self.tops = self.edges.max(axis=1)
+        self.bottoms = self.edges.min(axis=1)
+
+        # inintialize sweep front
+        # values in this list are indexes into self.pts
+        self.front = [0, 2, 1]
+        
+        # empty triangle list. 
+        # This will contain [(a, b, c), ...] where a,b,c are indexes into 
+        # self.pts
+        self.tris = OrderedDict()
+
+        # For each triangle, maps (a, b): c
+        # This is used to look up the thrid point in a triangle, given any 
+        # edge. Since each edge has two triangles, they are independently 
+        # stored as (a, b): c and (b, a): d
+        self.edges_lookup = {}
+
+    def triangulate(self):
+        self.initialize()
+        
+        pts = self.pts
+        front = self.front
+        
+        ## Begin sweep (sec. 3.4)
+        for i in range(3, pts.shape[0]):
+            pi = pts[i]
+            #debug("========== New point %d: %s ==========" % (i, pi))
+            
+            # First, triangulate from front to new point
+            # This applies to both "point events" (3.4.1) 
+            # and "edge events" (3.4.2).
+
+            # get index along front that intersects pts[i]
+            l = 0
+            while pts[front[l+1], 0] <= pi[0]:
+                l += 1
+            pl = pts[front[l]]
+            
+            # "(i) middle case"
+            if pi[0] > pl[0]:  
+                #debug("  mid case")
+                # Add a single triangle connecting pi,pl,pr
+                self.add_tri(front[l], front[l+1], i)
+                front.insert(l+1, i)
+            # "(ii) left case"
+            else:
+                #debug("  left case")
+                # Add triangles connecting pi,pl,ps and pi,pl,pr
+                self.add_tri(front[l], front[l+1], i)
+                self.add_tri(front[l-1], front[l], i)
+                front[l] = i
+            
+            #debug(front)
+                
+            # Continue adding triangles to smooth out front
+            # (heuristics shown in figs. 9, 10)
+            #debug("Smoothing front...")
+            for direction in -1, 1:
+                while True:
+                    # Find point connected to pi
+                    ind0 = front.index(i)
+                    ind1 = ind0 + direction
+                    ind2 = ind1 + direction
+                    if ind2 < 0 or ind2 >= len(front):
+                        break
+                    
+                    # measure angle made with front
+                    p1 = pts[front[ind1]]
+                    p2 = pts[front[ind2]]
+                    err = np.geterr()
+                    np.seterr(invalid='ignore')
+                    try:
+                        angle = np.arccos(self.cosine(pi, p1, p2))
+                    finally:
+                        np.seterr(**err)
+                    
+                    # if angle is < pi/2, make new triangle
+                    #debug("Smooth angle:", pi, p1, p2, angle)
+                    if angle > np.pi/2. or np.isnan(angle):
+                        break
+                    
+                    assert (i != front[ind1] and 
+                            front[ind1] != front[ind2] and 
+                            front[ind2] != i)
+                    self.add_tri(i, front[ind1], front[ind2], source='smooth1')
+                    front.pop(ind1)
+            #debug("Finished smoothing front.")
+            
+            # "edge event" (sec. 3.4.2)
+            # remove any triangles cut by completed edges and re-fill 
+            # the holes.
+            if i in self.tops:
+                for j in self.bottoms[self.tops == i]:
+                    # Make sure edge (j, i) is present in mesh
+                    # because edge event may have created a new front list
+                    self.edge_event(i, j)  
+                    front = self.front 
+                
+        self.finalize()
+        
+        self.tris = np.array(list(self.tris.keys()), dtype=int)
+        
+        #debug("Finished with %d tris:" % self.tris.shape[0])
+        #debug(str(self.tris))
+        
+    def finalize(self):
+        ## Finalize (sec. 3.5)
+
+        # (i) Add bordering triangles to fill hull
+        #debug("== Fill hull")
+        front = list(OrderedDict.fromkeys(self.front))
+
+        l = len(front) - 2
+        k = 1
+        while k < l-1:
+            # if edges lie in counterclockwise direction, then signed area 
+            # is positive
+            if self.iscounterclockwise(front[k], front[k+1], front[k+2]):
+                self.add_tri(front[k], front[k+1], front[k+2], legal=False, 
+                             source='fill_hull')
+                front.pop(k+1)
+                l -= 1
+                continue
+            k += 1
+
+        # (ii) Remove all triangles not inside the hull 
+        #      (not described in article)
+        #debug("== Remove triangles outside hull")
+
+        tris = []  # triangles to check
+        tri_state = {}  # 0 for outside, 1 for inside
+        
+        # find a starting triangle
+        for t in self.tris:
+            if 0 in t or 1 in t:
+                tri_state[t] = 0
+                tris.append(t)
+                break
+        
+        while tris:
+            #debug("iterate:", tris)
+            next_tris = []
+            for t in tris:
+                v = tri_state[t]
+                for i in (0, 1, 2):
+                    edge = (t[i], t[(i + 1) % 3])
+                    pt = t[(i + 2) % 3]
+                    t2 = self.adjacent_tri(edge, pt)
+                    if t2 is None:
+                        continue
+                    t2a = t2[1:3] + t2[0:1]
+                    t2b = t2[2:3] + t2[0:2]
+                    if t2 in tri_state or t2a in tri_state or t2b in tri_state:
+                        continue
+                    if self.is_constraining_edge(edge):
+                        tri_state[t2] = 1 - v
+                    else:
+                        tri_state[t2] = v
+                    next_tris.append(t2)
+            tris = next_tris
+        
+        for t, v in tri_state.items():
+            if v == 0:
+                self.remove_tri(*t)
+
+    def edge_event(self, i, j):
+        """
+        Force edge (i, j) to be present in mesh. 
+        This works by removing intersected triangles and filling holes up to
+        the cutting edge.
+        """
+        front_index = self.front.index(i)
+        
+        #debug("  == edge event ==")
+        front = self.front
+
+        # First just see whether this edge is already present
+        # (this is not in the published algorithm)
+        if (i, j) in self.edges_lookup or (j, i) in self.edges_lookup:
+            #debug("    already added.")
+            return
+        #debug("    Edge (%d,%d) not added yet. Do edge event. (%s - %s)" % 
+        #      (i, j, pts[i], pts[j]))
+        
+        # traverse in two different modes:
+        #  1. If cutting edge is below front, traverse through triangles. These
+        #     must be removed and the resulting hole re-filled. (fig. 12)
+        #  2. If cutting edge is above the front, then follow the front until 
+        #     crossing under again. (fig. 13)
+        # We must be able to switch back and forth between these 
+        # modes (fig. 14)
+
+        # Collect points that draw the open polygons on either side of the 
+        # cutting edge. Note that our use of 'upper' and 'lower' is not strict;
+        # in some cases the two may be swapped.
+        upper_polygon = [i]
+        lower_polygon = [i]
+        
+        # Keep track of which section of the front must be replaced
+        # and with what it should be replaced
+        front_holes = []  # contains indexes for sections of front to remove
+        
+        next_tri = None   # next triangle to cut (already set if in mode 1)
+        last_edge = None  # or last triangle edge crossed (if in mode 1)
+        
+        # Which direction to traverse front
+        front_dir = 1 if self.pts[j][0] > self.pts[i][0] else -1
+                
+        # Initialize search state
+        if self.edge_below_front((i, j), front_index):
+            mode = 1  # follow triangles
+            tri = self.find_cut_triangle((i, j))
+            last_edge = self.edge_opposite_point(tri, i)
+            next_tri = self.adjacent_tri(last_edge, i)
+            assert next_tri is not None
+            self.remove_tri(*tri)
+            # todo: does this work? can we count on last_edge to be clockwise
+            # around point i?
+            lower_polygon.append(last_edge[1])
+            upper_polygon.append(last_edge[0])
+        else:
+            mode = 2  # follow front
+
+        # Loop until we reach point j
+        while True:
+            #debug("  == edge_event loop: mode %d ==" % mode)
+            #debug("      front_holes:", front_holes, front)
+            #debug("      front_index:", front_index)
+            #debug("      next_tri:", next_tri)
+            #debug("      last_edge:", last_edge)
+            #debug("      upper_polygon:", upper_polygon)
+            #debug("      lower_polygon:", lower_polygon)
+            #debug("      =====")
+            if mode == 1:
+                # crossing from one triangle into another
+                if j in next_tri:
+                    #debug("    -> hit endpoint!")
+                    # reached endpoint! 
+                    # update front / polygons
+                    upper_polygon.append(j)
+                    lower_polygon.append(j)
+                    #debug("    Appended to upper_polygon:", upper_polygon)
+                    #debug("    Appended to lower_polygon:", lower_polygon)
+                    self.remove_tri(*next_tri)
+                    break
+                else:
+                    # next triangle does not contain the end point; we will
+                    # cut one of the two far edges.
+                    tri_edges = self.edges_in_tri_except(next_tri, last_edge)
+                    
+                    # select the edge that is cut
+                    last_edge = self.intersected_edge(tri_edges, (i, j))
+                    #debug("    set last_edge to intersected edge:", last_edge)
+                    last_tri = next_tri
+                    next_tri = self.adjacent_tri(last_edge, last_tri)
+                    #debug("    set next_tri:", next_tri)
+                    self.remove_tri(*last_tri)
+
+                    # Crossing an edge adds one point to one of the polygons
+                    if lower_polygon[-1] == last_edge[0]:
+                        upper_polygon.append(last_edge[1])
+                        #debug("    Appended to upper_polygon:", upper_polygon)
+                    elif lower_polygon[-1] == last_edge[1]:
+                        upper_polygon.append(last_edge[0])
+                        #debug("    Appended to upper_polygon:", upper_polygon)
+                    elif upper_polygon[-1] == last_edge[0]:
+                        lower_polygon.append(last_edge[1])
+                        #debug("    Appended to lower_polygon:", lower_polygon)
+                    elif upper_polygon[-1] == last_edge[1]:
+                        lower_polygon.append(last_edge[0])
+                        #debug("    Appended to lower_polygon:", lower_polygon)
+                    else:
+                        raise RuntimeError("Something went wrong..")
+                    
+                    # If we crossed the front, go to mode 2
+                    x = self.edge_in_front(last_edge)
+                    if x >= 0:  # crossing over front
+                        #debug("    -> crossed over front, prepare for mode 2")
+                        mode = 2
+                        next_tri = None
+                        #debug("    set next_tri: None")
+                        
+                        # where did we cross the front?
+                        # nearest to new point
+                        front_index = x + (1 if front_dir == -1 else 0)
+                        #debug("    set front_index:", front_index)
+                        
+                        # Select the correct polygon to be lower_polygon
+                        # (because mode 2 requires this). 
+                        # We know that last_edge is in the front, and 
+                        # front[front_index] is the point _above_ the front. 
+                        # So if this point is currently the last element in
+                        # lower_polygon, then the polys must be swapped.
+                        if lower_polygon[-1] == front[front_index]:
+                            tmp = lower_polygon, upper_polygon
+                            upper_polygon, lower_polygon = tmp
+                            #debug('    Swap upper/lower polygons')
+                        else:
+                            assert upper_polygon[-1] == front[front_index]
+                        
+                    else:
+                        assert next_tri is not None
+                
+            else:  # mode == 2
+                # At each iteration, we require:
+                #   * front_index is the starting index of the edge _preceding_
+                #     the edge that will be handled in this iteration
+                #   * lower_polygon is the polygon to which points should be
+                #     added while traversing the front
+                
+                front_index += front_dir
+                #debug("    Increment front_index: %d" % front_index)
+                next_edge = (front[front_index], front[front_index+front_dir])
+                #debug("    Set next_edge: %s" % repr(next_edge))
+                
+                assert front_index >= 0
+                if front[front_index] == j:
+                    # found endpoint!
+                    #debug("    -> hit endpoint!")
+                    lower_polygon.append(j)
+                    upper_polygon.append(j)
+                    #debug("    Appended to upper_polygon:", upper_polygon)
+                    #debug("    Appended to lower_polygon:", lower_polygon)
+                    break
+
+                # Add point to lower_polygon. 
+                # The conditional is because there are cases where the 
+                # point was already added if we just crossed from mode 1.
+                if lower_polygon[-1] != front[front_index]:
+                    lower_polygon.append(front[front_index])
+                    #debug("    Appended to lower_polygon:", lower_polygon)
+
+                front_holes.append(front_index)
+                #debug("    Append to front_holes:", front_holes)
+
+                if self.edges_intersect((i, j), next_edge):
+                    # crossing over front into triangle
+                    #debug("    -> crossed over front, prepare for mode 1")
+                    mode = 1
+                    
+                    last_edge = next_edge
+                    #debug("    Set last_edge:", last_edge)
+                    
+                    # we are crossing the front, so this edge only has one
+                    # triangle. 
+                    next_tri = self.tri_from_edge(last_edge)
+                    #debug("    Set next_tri:", next_tri)
+                    
+                    upper_polygon.append(front[front_index+front_dir])
+                    #debug("    Appended to upper_polygon:", upper_polygon)
+                #else:
+                    #debug("    -> did not cross front..")
+        
+        #debug("Finished edge_event:")
+        #debug("  front_holes:", front_holes)
+        #debug("  upper_polygon:", upper_polygon)
+        #debug("  lower_polygon:", lower_polygon)
+
+        # (iii) triangluate empty areas
+        
+        #debug("Filling edge_event polygons...")
+        for polygon in [lower_polygon, upper_polygon]:
+            dist = self.distances_from_line((i, j), polygon)
+            #debug("Distances:", dist)
+            while len(polygon) > 2:
+                ind = np.argmax(dist)
+                #debug("Next index: %d" % ind)
+                self.add_tri(polygon[ind], polygon[ind-1],
+                             polygon[ind+1], legal=False, 
+                             source='edge_event')
+                polygon.pop(ind)
+                dist.pop(ind)
+
+        #debug("Finished filling edge_event polygons.")
+        
+        # update front by removing points in the holes (places where front 
+        # passes below the cut edge)
+        front_holes.sort(reverse=True)
+        for i in front_holes:
+            front.pop(i)
+
+        #debug("Finished updating front after edge_event.")
+        
+    def find_cut_triangle(self, edge):
+        """
+        Return the triangle that has edge[0] as one of its vertices and is 
+        bisected by edge.
+        
+        Return None if no triangle is found.
+        """
+        edges = []  # opposite edge for each triangle attached to edge[0]
+        for tri in self.tris:
+            if edge[0] in tri:
+                edges.append(self.edge_opposite_point(tri, edge[0]))
+                
+        for oedge in edges:
+            o1 = self.orientation(edge, oedge[0])
+            o2 = self.orientation(edge, oedge[1]) 
+            #debug(edge, oedge, o1, o2)
+            #debug(self.pts[np.array(edge)])
+            #debug(self.pts[np.array(oedge)])
+            if o1 != o2:
+                return (edge[0], oedge[0], oedge[1])
+        
+        return None
+
+    def edge_in_front(self, edge):
+        """ Return the index where *edge* appears in the current front.
+        If the edge is not in the front, return -1
+        """
+        e = (list(edge), list(edge)[::-1])
+        for i in range(len(self.front)-1):
+            if self.front[i:i+2] in e:
+                return i
+        return -1
+
+    def edge_opposite_point(self, tri, i):
+        """ Given a triangle, return the edge that is opposite point i.
+        Vertexes are returned in the same orientation as in tri.
+        """
+        ind = tri.index(i)
+        return (tri[(ind+1) % 3], tri[(ind+2) % 3])
+
+    def adjacent_tri(self, edge, i):
+        """
+        Given a triangle formed by edge and i, return the triangle that shares
+        edge. *i* may be either a point or the entire triangle.
+        """
+        if not np.isscalar(i):
+            i = [x for x in i if x not in edge][0]
+
+        try:
+            pt1 = self.edges_lookup[edge]
+            pt2 = self.edges_lookup[(edge[1], edge[0])]
+        except KeyError:
+            return None
+            
+        if pt1 == i:
+            return (edge[1], edge[0], pt2)
+        elif pt2 == i:
+            return (edge[1], edge[0], pt1)
+        else:
+            raise RuntimeError("Edge %s and point %d do not form a triangle "
+                               "in this mesh." % (edge, i))
+
+    def tri_from_edge(self, edge):
+        """Return the only tri that contains *edge*. If two tris share this
+        edge, raise an exception.
+        """
+        edge = tuple(edge)
+        p1 = self.edges_lookup.get(edge, None)
+        p2 = self.edges_lookup.get(edge[::-1], None)
+        if p1 is None:
+            if p2 is None:
+                raise RuntimeError("No tris connected to edge %r" % (edge,))
+            return edge + (p2,)
+        elif p2 is None:
+            return edge + (p1,)
+        else:
+            raise RuntimeError("Two triangles connected to edge %r" % (edge,))
+
+    def edges_in_tri_except(self, tri, edge):
+        """Return the edges in *tri*, excluding *edge*.
+        """
+        edges = [(tri[i], tri[(i+1) % 3]) for i in range(3)]
+        try:
+            edges.remove(tuple(edge))
+        except ValueError:
+            edges.remove(tuple(edge[::-1]))
+        return edges
+
+    def edge_below_front(self, edge, front_index):
+        """Return True if *edge* is below the current front. 
+        
+        One of the points in *edge* must be _on_ the front, at *front_index*.
+        """
+        f0 = self.front[front_index-1]
+        f1 = self.front[front_index+1]
+        return (self.orientation(edge, f0) > 0 and 
+                self.orientation(edge, f1) < 0)
+
+    def is_constraining_edge(self, edge):
+        mask1 = self.edges == edge[0]
+        mask2 = self.edges == edge[1]
+        return (np.any(mask1[:, 0] & mask2[:, 1]) or 
+                np.any(mask2[:, 0] & mask1[:, 1]))
+    
+    def intersected_edge(self, edges, cut_edge):
+        """ Given a list of *edges*, return the first that is intersected by
+        *cut_edge*.
+        """
+        for edge in edges:
+            if self.edges_intersect(edge, cut_edge):
+                return edge
+
+    def find_edge_intersections(self):
+        """
+        Return a dictionary containing, for each edge in self.edges, a list
+        of the positions at which the edge should be split.
+        """
+        edges = self.pts[self.edges]
+        cuts = {}  # { edge: [(intercept, point), ...], ... }
+        for i in range(edges.shape[0]-1):
+            # intersection of edge i onto all others
+            int1 = self.intersect_edge_arrays(edges[i:i+1], edges[i+1:])
+            # intersection of all edges onto edge i
+            int2 = self.intersect_edge_arrays(edges[i+1:], edges[i:i+1])
+        
+            # select for pairs that intersect
+            err = np.geterr()
+            np.seterr(divide='ignore', invalid='ignore')
+            try:
+                mask1 = (int1 >= 0) & (int1 <= 1)
+                mask2 = (int2 >= 0) & (int2 <= 1)
+                mask3 = mask1 & mask2  # all intersections
+            finally:
+                np.seterr(**err)
+            
+            # compute points of intersection
+            inds = np.argwhere(mask3)[:, 0]
+            if len(inds) == 0:
+                continue
+            h = int2[inds][:, np.newaxis]
+            pts = (edges[i, 0][np.newaxis, :] * (1.0 - h) + 
+                   edges[i, 1][np.newaxis, :] * h)
+            
+            # record for all edges the location of cut points
+            edge_cuts = cuts.setdefault(i, [])
+            for j, ind in enumerate(inds):
+                if 0 < int2[ind] < 1:
+                    edge_cuts.append((int2[ind], pts[j]))
+                if 0 < int1[ind] < 1:
+                    other_cuts = cuts.setdefault(ind+i+1, [])
+                    other_cuts.append((int1[ind], pts[j]))
+        
+        # sort all cut lists by intercept, remove duplicates
+        for k, v in cuts.items():
+            v.sort(key=lambda x: x[0])
+            for i in range(len(v)-2, -1, -1):
+                if v[i][0] == v[i+1][0]:
+                    v.pop(i+1)
+        return cuts
+
+    def split_intersecting_edges(self):
+        # we can do all intersections at once, but this has excessive memory
+        # overhead.
+        #int1 = self.intersection_matrix(edges)
+        #int2 = int1.T
+        
+        # measure intersection point between all pairs of edges
+        all_cuts = self.find_edge_intersections()
+
+        # cut edges at each intersection
+        add_pts = []
+        add_edges = []
+        for edge, cuts in all_cuts.items():
+            if len(cuts) == 0:
+                continue
+            
+            #debug("Edge intersections:", edge, self.edges[edge], 
+            #      self.pts[self.edges[edge]], cuts)
+            
+            # add new points
+            pt_offset = self.pts.shape[0] + len(add_pts)
+            new_pts = [x[1] for x in cuts]
+            add_pts.extend(new_pts)
+            #debug("Add new points:", new_pts)
+            
+            # list of point indexes for all new edges
+            pt_indexes = list(range(pt_offset, pt_offset + len(cuts)))
+            pt_indexes.append(self.edges[edge, 1])
+            
+            # modify original edge
+            self.edges[edge, 1] = pt_indexes[0]
+            
+            # add new edges
+            new_edges = [[pt_indexes[i-1], pt_indexes[i]] 
+                         for i in range(1, len(pt_indexes))] 
+            add_edges.extend(new_edges)
+            
+        #debug("Adding %d points and %d edges to remove intersections." % 
+        #      (len(add_pts), len(add_edges)))
+        if add_pts:
+            add_pts = np.array(add_pts, dtype=self.pts.dtype)
+            self.pts = np.append(self.pts, add_pts, axis=0)
+        if add_edges:
+            add_edges = np.array(add_edges, dtype=self.edges.dtype)
+            self.edges = np.append(self.edges, add_edges, axis=0)
+
+    def merge_duplicate_points(self):
+        # generate a list of all pairs (i,j) of identical points
+        dups = []
+        for i in range(self.pts.shape[0]-1):
+            test_pt = self.pts[i:i+1]
+            comp_pts = self.pts[i+1:]
+            eq = test_pt == comp_pts
+            eq = eq[:, 0] & eq[:, 1]
+            for j in np.argwhere(eq)[:, 0]:
+                dups.append((i, i+1+j))
+
+        dups_arr = np.array(dups)
+        # remove duplicate points
+        pt_mask = np.ones(self.pts.shape[0], dtype=bool)
+        for i, inds in enumerate(dups_arr):
+            # remove j from points
+            # (note we pull the index from the original dups instead of 
+            # dups_arr because the indexes in pt_mask do not change)
+            pt_mask[dups[i][1]] = False
+            
+            i, j = inds
+            
+            # rewrite edges to use i instead of j
+            self.edges[self.edges == j] = i
+            
+            #assert not np.any(self.edges[:,0] == self.edges[:,1])
+            
+            # decrement all point indexes > j
+            self.edges[self.edges > j] -= 1
+            dups_arr[dups_arr > j] -= 1
+            #assert not np.any(self.edges[:,0] == self.edges[:,1])
+        
+        self.pts = self.pts[pt_mask]
+        
+        # remove zero-length edges
+        mask = self.edges[:, 0] != self.edges[:, 1]
+        self.edges = self.edges[mask]
+    
+    def distance(self, A, B):
+        # Distance between points A and B
+        n = len(A)
+        assert len(B) == n
+        return np.linalg.norm(np.array(list(A)) - np.array(list(B)))
+
+    def distances_from_line(self, edge, points):
+        # Distance of a set of points from a given line
+        #debug("distance from %r to %r" % (points, edge))
+        e1 = self.pts[edge[0]]
+        e2 = self.pts[edge[1]]
+        distances = []
+        for i in points:
+            p = self.pts[i]
+            proj = self.projection(e1, p, e2)
+            distances.append(((p - proj)**2).sum()**0.5)
+        assert distances[0] == 0 and distances[-1] == 0
+        return distances
+
+    def projection(self, a, b, c):
+        """Return projection of (a,b) onto (a,c)
+        Arguments are point locations, not indexes.
+        """
+        ab = b - a
+        ac = c - a
+        return a + ((ab*ac).sum() / (ac*ac).sum()) * ac
+
+    def cosine(self, A, B, C):
+        # Cosine of angle ABC
+        a = ((C - B)**2).sum()
+        b = ((C - A)**2).sum()
+        c = ((B - A)**2).sum()
+        d = (a + c - b) / ((4 * a * c)**0.5)
+        return d
+
+    #def barycentric(self, A, B, C, p, q, r):
+        ## Cartesian coordinates of the point whose barycentric coordinates
+        ## with respect to the triangle ABC are [p,q,r]
+        #n = len(A)
+        #assert len(B) == len(C) == n
+        #s = p+q+r
+        #p, q, r = p/s, q/s, r/s
+        #return tuple([p*A[i]+q*B[i]+r*C[i] for i in range(n)])
+
+    #def trilinear(self, A, B, C, alpha, beta, gamma):
+        ## Cartesian coordinates of the point whose trilinear coordinates
+        ## with respect to the triangle ABC are [alpha,beta,gamma]
+        #a = distance(B, C)
+        #b = distance(A, C)
+        #c = distance(A, B)
+        #return barycentric(A, B, C, a*alpha, b*beta, c*gamma)
+                
+    #def circuminfo(self, A, B, C):
+        ## Cartesian coordinates of the circumcenter of triangle ABC
+        #cosA = cosine(C, A, B)
+        #cosB = cosine(A, B, C)
+        #cosC = cosine(B, C, A)
+        #cc = trilinear(A, B, C, cosA, cosB, cosC)
+        ## returns circumcenter and circumradius
+        #return cc, distance(cc, A)
+
+    def iscounterclockwise(self, a, b, c):
+        # Check if the points lie in counter-clockwise order or not
+        A = self.pts[a]
+        B = self.pts[b]
+        C = self.pts[c]
+        return np.cross(B-A, C-B) > 0
+
+    def edges_intersect(self, edge1, edge2):
+        """
+        Return 1 if edges intersect completely (endpoints excluded)
+        """
+        h12 = self.intersect_edge_arrays(self.pts[np.array(edge1)], 
+                                         self.pts[np.array(edge2)])
+        h21 = self.intersect_edge_arrays(self.pts[np.array(edge2)], 
+                                         self.pts[np.array(edge1)])
+        err = np.geterr()
+        np.seterr(divide='ignore', invalid='ignore')
+        try:
+            out = (0 < h12 < 1) and (0 < h21 < 1)
+        finally:
+            np.seterr(**err)
+        return out
+
+    def intersection_matrix(self, lines):
+        """
+        Return a 2D array of intercepts such that 
+        intercepts[i, j] is the intercept of lines[i] onto lines[j].
+        
+        *lines* must be an array of point locations with shape (N, 2, 2), where
+        the axes are (lines, points_per_line, xy_per_point).
+        
+        The intercept is described in intersect_edge_arrays().
+        """
+        return self.intersect_edge_arrays(lines[:, np.newaxis, ...], 
+                                          lines[np.newaxis, ...])
+        
+    def intersect_edge_arrays(self, lines1, lines2):
+        """Return the intercepts of all lines defined in *lines1* as they 
+        intersect all lines in *lines2*. 
+        
+        Arguments are of shape (..., 2, 2), where axes are:
+        
+        0: number of lines
+        1: two points per line
+        2: x,y pair per point
+
+        Lines are compared elementwise across the arrays (lines1[i] is compared
+        against lines2[i]). If one of the arrays has N=1, then that line is
+        compared against all lines in the other array.
+        
+        Returns an array of shape (N,) where each value indicates the intercept
+        relative to the defined line segment. A value of 0 indicates 
+        intersection at the first endpoint, and a value of 1 indicates 
+        intersection at the second endpoint. Values between 1 and 0 are on the
+        segment, whereas values outside 1 and 0 are off of the segment. 
+        """
+        # vector for each line in lines1
+        l1 = lines1[..., 1, :] - lines1[..., 0, :]
+        # vector for each line in lines2
+        l2 = lines2[..., 1, :] - lines2[..., 0, :]
+        # vector between first point of each line
+        diff = lines1[..., 0, :] - lines2[..., 0, :]
+        
+        p = l1.copy()[..., ::-1]  # vectors perpendicular to l1
+        p[..., 0] *= -1
+        
+        f = (l2 * p).sum(axis=-1)  # l2 dot p
+        # tempting, but bad idea! 
+        #f = np.where(f==0, 1, f)
+        err = np.geterr()
+        np.seterr(divide='ignore', invalid='ignore')
+        try:
+            h = (diff * p).sum(axis=-1) / f  # diff dot p / f
+        finally:
+            np.seterr(**err)
+        
+        return h
+
+    def orientation(self, edge, point):
+        """ Returns +1 if edge[0]->point is clockwise from edge[0]->edge[1], 
+        -1 if counterclockwise, and 0 if parallel.
+        """
+        v1 = self.pts[point] - self.pts[edge[0]]
+        v2 = self.pts[edge[1]] - self.pts[edge[0]]
+        c = np.cross(v1, v2)  # positive if v1 is CW from v2
+        return 1 if c > 0 else (-1 if c < 0 else 0)
+
+    #def legalize(self, p):
+        ### Legalize recursively - incomplete
+        #return p  # disabled for now
+    
+        #f00, f11, p = p
+
+        #debug("Legalizing points = {}, {}, {}".format(f00, f11, p))
+        #a = pts[f00]
+        #b = pts[f11]
+        #c = pts[p]
+        #cc, cr = circuminfo(a, b, c)
+        #for point in pts:
+        #    if np.all(point == a) or np.all(point == b) or np.all(point == c):
+        #        continue
+        #    elif distance(cc, point) < cr:
+        #        #debug("Illegal point")
+        #        #debug(point)
+        #        pass
+
+        #return (f00, f11, p)
+
+    def add_tri(self, a, b, c, legal=True, source=None):
+        # source is just used for #debugging
+        #debug("Add triangle [%s]:" % source, (a, b, c))
+        
+        # sanity check
+        assert a != b and b != c and c != a
+        
+        # ignore flat tris
+        pa = self.pts[a]
+        pb = self.pts[b]
+        pc = self.pts[c]
+        if np.all(pa == pb) or np.all(pb == pc) or np.all(pc == pa):
+            #debug("   Triangle is flat; refusing to add.")
+            return
+        
+        # check this tri is unique
+        for t in permutations((a, b, c)):
+            if t in self.tris:
+                raise Exception("Cannot add %s; already have %s" % 
+                                ((a, b, c), t))
+        
+        # TODO: should add to edges_lookup after legalization??
+        if self.iscounterclockwise(a, b, c):
+            #debug("    ", (a, b), (b, c), (c, a))
+            assert (a, b) not in self.edges_lookup
+            assert (b, c) not in self.edges_lookup
+            assert (c, a) not in self.edges_lookup
+            self.edges_lookup[(a, b)] = c
+            self.edges_lookup[(b, c)] = a
+            self.edges_lookup[(c, a)] = b
+        else:
+            #debug("    ", (b, a), (c, b), (a, c))
+            assert (b, a) not in self.edges_lookup
+            assert (c, b) not in self.edges_lookup
+            assert (a, c) not in self.edges_lookup
+            self.edges_lookup[(b, a)] = c
+            self.edges_lookup[(c, b)] = a
+            self.edges_lookup[(a, c)] = b
+        
+        #if legal:
+            #tri = self.legalize((a, b, c))
+        #else:
+        tri = (a, b, c)
+        
+        self.tris[tri] = None
+
+    def remove_tri(self, a, b, c):
+        #debug("Remove triangle:", (a, b, c))
+        
+        for k in permutations((a, b, c)):
+            if k in self.tris:
+                break
+        del self.tris[k]
+        (a, b, c) = k
+
+        if self.edges_lookup.get((a, b), -1) == c:
+            #debug("    ", (a,b), (b,c), (c,a))
+            del self.edges_lookup[(a, b)]
+            del self.edges_lookup[(b, c)]
+            del self.edges_lookup[(c, a)]
+        elif self.edges_lookup.get((b, a), -1) == c:
+            #debug("    ", (b,a), (c,b), (a,c))
+            del self.edges_lookup[(b, a)]
+            del self.edges_lookup[(a, c)]
+            del self.edges_lookup[(c, b)]
+        else:
+            raise RuntimeError("Lost edges_lookup for tri (%d, %d, %d)" % 
+                               (a, b, c))
+
+        return k
+
+
+# Note: using custom #debug instead of logging because 
+# there are MANY messages and logger might be too expensive.
+# After this becomes stable, we might just remove them altogether.
+def debug(*args):
+    print(*args)
diff --git a/vispy/gloo/__init__.py b/vispy/gloo/__init__.py
index 5b7d2c7..1901091 100644
--- a/vispy/gloo/__init__.py
+++ b/vispy/gloo/__init__.py
@@ -1,13 +1,13 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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
+This module implements classes for the things that are "objects" in
 OpenGL, such as textures, FBO's, VBO's and shaders. Further, some
-convenience classes are implemented (like the collection class?).
+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.
@@ -19,70 +19,40 @@ 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
+
+    # Draw 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:
-    
+    progress and there are still 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.
+      FrameBuffers, respectively) 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
-
+from __future__ import division
+
+from . import gl  # noqa
+from .globject import GLObject  # noqa
+from .buffer import VertexBuffer, IndexBuffer  # noqa
+from .initialize import gl_initialize  # noqa
+from .texture import Texture2D, TextureAtlas, Texture3D  # noqa
+from .shader import VertexShader, FragmentShader  # noqa
+from .program import Program  # noqa
+from .framebuffer import (FrameBuffer, ColorBuffer, DepthBuffer,  # noqa
+                          StencilBuffer)  # noqa
+from .wrappers import *  # noqa
diff --git a/vispy/gloo/buffer.py b/vispy/gloo/buffer.py
index e75b8a6..dd49e5d 100644
--- a/vispy/gloo/buffer.py
+++ b/vispy/gloo/buffer.py
@@ -1,870 +1,778 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team. All Rights Reserved.
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, 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
-
+from . globject import GLObject
+from ..util import logger
 
 
 # ------------------------------------------------------------ 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.
-    
+    """ Generic GPU buffer.
+
+    A generic buffer is an interface used to upload data to a GPU array buffer
+    (gl.GL_ARRAY_BUFFER or gl.GL_ELEMENT_ARRAY_BUFFER). It keeps track of
+    buffer size but does not have any CPU storage. You can consider it as
+    write-only.
+
+    The `set_data` is a deferred operation: you can call it even if an OpenGL
+    context is not available. The `update` function is responsible to upload
+    pending data to GPU memory and requires an active GL context.
+
+    The Buffer class only deals with data in terms of bytes; it is not
+    aware of data type or element size.
+
     Parameters
     ----------
-    target : GLENUM
+    target : GLenum
         gl.GL_ARRAY_BUFFER or gl.GL_ELEMENT_ARRAY_BUFFER
     data : ndarray
-        The data to set. Optional.
+        Buffer data
+    nbytes : int
+        Buffer byte size
     """
 
-
-    def __init__(self, target, data=None):
-        """ Initialize buffer into default state. """
+    def __init__(self, data=None, target=gl.GL_ARRAY_BUFFER, nbytes=None):
 
         GLObject.__init__(self)
-        
+        self._views = []
+        self._valid = True
+
+        # For ATI bug
+        self._bufferSubDataOk = False
+
         # Store and check target
         if target not in (gl.GL_ARRAY_BUFFER, gl.GL_ELEMENT_ARRAY_BUFFER):
-            raise ValueError("Invalid target for buffer object.")
+            raise ValueError("Invalid target for buffer object")
         self._target = target
-        
-        # Total bytes consumed by the elements of the buffer
+
+        # Bytesize of buffer in GPU memory
+        self._buffer_size = None
+        # Bytesize of buffer in CPU memory
         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
+            if nbytes is not None:
+                raise ValueError("Cannot specify both data and nbytes.")
+            self.set_data(data, copy=False)
+        elif nbytes is not None:
+            self._nbytes = nbytes
         
-        # Clear pending subdata
-        self._pending_data = []
-    
+    @property
+    def nbytes(self):
+        """ Buffer byte size """
+
+        return self._nbytes
     
-    def set_data(self, data):
-        """ Set the bytes data. This accepts a numpy array,
-        but the data is not checked for dtype or shape.
+    def set_subdata(self, data, offset=0, copy=False):
+        """ Set a sub-region of the buffer (deferred operation).
         
         Parameters
         ----------
+
         data : ndarray
-            The data to set.
+            Data to be uploaded
+        offset: int
+            Offset in buffer where to start copying data (in bytes)
+        copy: bool
+            Since the operation is deferred, data may change before
+            data is actually uploaded to GPU memory.
+            Asking explicitly for a copy will prevent this behavior.
         """
-        
-        # 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!
+        data = np.array(data, copy=copy)
         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.
+
+        if offset < 0:
+            raise ValueError("Offset must be positive")
+        elif (offset + nbytes) > self._nbytes:
+            raise ValueError("Data does not fit into buffer")
+
+        # If the whole buffer is to be written, we clear any pending data
+        # (because they will be overwritten anyway)
+        if nbytes == self._nbytes and offset == 0:
+            self._pending_data = []
+        self._pending_data.append((data, nbytes, offset))
+
+    def set_data(self, data, copy=False):
+        """ Set data in the buffer (deferred operation).
         
+        This completely resets the size and contents of the buffer.
+
         Parameters
         ----------
-        offset : int
-            The offset (in bytes) at which to set the given data.
+
         data : ndarray
-            The data to set.
-        
+            Data to be uploaded
+        copy: bool
+            Since the operation is deferred, data may change before
+            data is actually uploaded to GPU memory.
+            Asking explicitly for a copy will prevent this behavior.
         """
-        
-        # 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)
+        data = np.array(data, copy=copy)
         nbytes = data.nbytes
+
+        if nbytes != self._nbytes:
+            self.resize_bytes(nbytes)
+
+        # We can discard any other pending operations here.
+        self._pending_data = [(data, nbytes, 0)]
+
+    def resize_bytes(self, size):
+        """ Resize this buffer (deferred operation). 
         
-        # 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
+        Parameters
+        ----------
+        size : int
+            New buffer size in bytes.
+        """
+        self._nbytes = size
+        self._pending_data = []
+        # Invalidate any view on this buffer
+        for view in self._views:
+            view._valid = False
+        self._views = []
 
-    
     def _create(self):
         """ Create buffer on GPU """
-        if not self._handle:
-            self._handle = gl.glGenBuffers(1)
-    
-    
+
+        logger.debug("GPU: Creating buffer")
+        self._handle = gl.glCreateBuffer()
+
     def _delete(self):
         """ Delete buffer from GPU """
-        gl.glDeleteBuffers(1 , [self._handle])
-    
-    
+
+        logger.debug("GPU: Deleting buffer")
+        gl.glDeleteBuffer(self._handle)
+
+    def _resize_bytes(self):
+        """ """
+
+        logger.debug("GPU: Resizing buffer(%d bytes)" % self._nbytes)
+        gl.glBufferData(self._target, self._nbytes, self._usage)
+        self._buffer_size = self._nbytes
+
     def _activate(self):
         """ Bind the buffer to some target """
-        gl.glBindBuffer(self._target, self._handle)
-
 
+        logger.debug("GPU: Activating buffer")
+        gl.glBindBuffer(self._target, self._handle)
+        
+        # Resize if necessary
+        if self._buffer_size != self._nbytes:
+            self._resize_bytes()
+        
+        # Update pending data if necessary
+        if self._pending_data:
+            logger.debug("GPU: Updating buffer (%d pending operation(s))" %
+                         len(self._pending_data))
+            self._update_data()
+    
     def _deactivate(self):
         """ Unbind the current bound buffer """
-        gl.glBindBuffer(self._target, 0)
 
+        logger.debug("GPU: Deactivating buffer")
+        gl.glBindBuffer(self._target, 0)
 
-    def _update(self):
+    def _update_data(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
+
+        # Update 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))
+
+            # Determine whether to check errors to try handling the ATI bug
+            check_ati_bug = ((not self._bufferSubDataOk) and
+                             (gl.current_backend is gl.desktop) and
+                             sys.platform.startswith('win'))
+
+            # flush any pending errors
+            if check_ati_bug:
+                gl.check_error('periodic check')
+
             try:
-                gl.glBufferSubData(self._target, offset, nbytes, data)
-            except Exception as error:
+                gl.glBufferSubData(self._target, offset, data)
+                if check_ati_bug:
+                    gl.check_error('glBufferSubData')
+                self._bufferSubDataOk = True  # glBufferSubData seems to work
+            except Exception:
                 # 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)
+                if offset == 0 and nbytes == self._nbytes:
+                    gl.glBufferData(self._target, data, self._usage)
+                    logger.debug("Using glBufferData instead of " +
+                                 "glBufferSubData (known ATI bug).")
                 else:
                     raise
 
 
-
-
-# ------------------------------------------------------ DataBuffer class ---
+# -------------------------------------------------------- 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.
-    
+    """ GPU data buffer that is aware of data type and elements size
+
     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.
-    
+    data : ndarray
+        Buffer data
+    dtype : dtype
+        Buffer data type
+    size : int
+        Number of elements in buffer
+    base : DataBuffer
+        Base buffer of this buffer
+    offset : int
+        Byte offset of this buffer relative to base buffer
+    store : bool
+        Specify whether this object stores a reference to the data,
+        allowing the data to be updated regardless of striding. Note
+        that modifying the data after passing it here might result in
+        undesired behavior, unless a copy is given. Default True.
     """
 
+    def __init__(self, data=None, dtype=None, target=gl.GL_ARRAY_BUFFER,
+                 size=0, store=True):
+        self._data = None
+        self._store = store
+        self._copied = False  # flag to indicate that a copy is made
+        self._size = size  # number of elements in buffer
 
-    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) )
+        # Convert data to array+dtype if needed
+        if data is not None:
+            if dtype is not None:
+                data = np.array(data, dtype=dtype, copy=False)
+            else:
+                data = np.array(data, copy=False)
+
+        # Create buffer from dtype and size
+        elif dtype is not None:
+            self._dtype = np.dtype(dtype)
+            self._size = size
+            self._stride = self._dtype.itemsize
+            self._itemsize = self._dtype.itemsize
+            self._nbytes = self._size * self._itemsize
+            if self._store:
+                self._data = np.empty(self._size, dtype=self._dtype)
+            # else:
+            #    self.set_data(data,copy=True)
+
+        # We need a minimum amount of information
         else:
-            if self.dtype.name not in self.DTYPE2GTYPE:
-                    raise TypeError("Data type not allowed for %s: %s" % 
-                                (self.__class__.__name__, self.dtype.name) )
+            raise ValueError("data/dtype/base cannot be all set to None")
         
+        Buffer.__init__(self, data=data, target=target)
+
+    @property
+    def target(self):
+        """ OpenGL type of object. """
+
+        return self._target
+
+    def _prepare_data(self, data, **kwds):
+        if len(kwds) > 0:
+            raise ValueError("Unexpected keyword arguments: %r" %
+                             list(kwds.keys()))
+        # Subclasses override this
+        return data
+
+    def set_subdata(self, data, offset=0, copy=False, **kwds):
+        data = self._prepare_data(data, **kwds)
+        offset = offset * self.itemsize
+        Buffer.set_subdata(self, data=data, offset=offset, copy=copy)
     
-    
-    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
+    def set_data(self, data, copy=False, **kwds):
+        """ Set data (deferred operation)
+
+        Parameters
+        ----------
+
+        data : ndarray
+            Data to be uploaded
+        offset: int
+            Offset in buffer to start copying data (in number of vertices)
+        copy: bool
+            Since the operation is deferred, data may change before
+            data is actually uploaded to GPU memory.
+            Asking explicitly for a copy will prevent this behavior.
         """
-        raise NotImplementedError()
-    
-    
+        data = self._prepare_data(data, **kwds)
+        
+        # Handle storage
+        if self._store:
+            if not data.flags["C_CONTIGUOUS"]:
+                logger.warning("Copying discontiguous data as CPU storage")
+                self._copied = True
+                data = data.copy()
+            self._data = data.ravel()  # Makes a copy if not contiguous
+        # Store meta data (AFTER flattening, or stride would be wrong)
+        self._dtype = data.dtype
+        self._stride = data.strides[-1]
+        self._itemsize = self._dtype.itemsize
+        Buffer.set_data(self, data=data, copy=copy)
+
     @property
     def dtype(self):
-        """ The buffer data type. """
+        """ Buffer dtype """
+
         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
-    
-    
+    def offset(self):
+        """ Buffer offset (in bytes) relative to base """
+
+        return 0
+
     @property
     def stride(self):
-        """ The number of bytes separating two elements. """
+        """ Stride of data in memory """
+
         return self._stride
-    
-    
+
     @property
-    def count(self):
-        """ The number of vertices in the buffer. """
-        return self._count
-    
-    
+    def size(self):
+        """ Number of elements in the buffer """
+        return self._size
+
     @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.
-        
+    def data(self):
+        """ Buffer CPU storage """
+
+        return self._data
+
+    @property
+    def itemsize(self):
+        """ The total number of bytes required to store the array data """
+
+        return self._itemsize
+
+    @property
+    def glsl_type(self):
+        """ GLSL declaration strings required for a variable to hold this data.
         """
-        
-        # 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.
-        
+        dtshape = self.dtype[0].shape
+        n = dtshape[0] if dtshape else 1
+        if n > 1:
+            dtype = 'vec%d' % n
+        else:
+            dtype = 'float' if 'f' in self.dtype[0].base.kind else 'int'
+        return 'attribute', dtype
+
+    def resize_bytes(self, size):
+        """ Resize the buffer (in-place, deferred operation)
+
         Parameters
         ----------
-        offset : int
-            The offset (in vertex indices) to set the data for.
-        data : np.ndarray
-            The data to update.
+        size : integer
+            New buffer size in bytes
+
+        Notes
+        -----
+        This clears any pending operations.
         """
+        Buffer.resize_bytes(self, size)
+
+        self._size = size // self.itemsize
         
-        # 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)
+        if self._data is not None and self._store: 
+            if self._data.size != self._size:
+                self._data = np.resize(self._data, self._size)
+        else:
+            self._data = None
 
+    def __getitem__(self, key):
+        """ Create a view on this buffer. """
 
+        view = DataBufferView(self, key)
+        self._views.append(view)
+        return view
 
-# ------------------------------------------------------ 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
+    def __setitem__(self, key, data):
+        """ Set data (deferred operation) """
+
+        # Setting a whole field of the buffer: only allowed if we have CPU
+        # storage. Note this case (key is str) only happen with base buffer
+        if isinstance(key, str):
+            if self._data is None:
+                raise ValueError(
+                    """Cannot set non contiguous """
+                    """data on buffer without CPU storage""")
+
+            # WARNING: do we check data size
+            #          or do we let numpy raises an error ?
+            self._data[key] = data
+            self.set_data(self._data, copy=False)
+            return
+
+        # Setting one or several elements
+        elif isinstance(key, int):
+            if key < 0:
+                key += self.size
+            if key < 0 or key > self.size:
+                raise IndexError("Buffer assignment index out of range")
+            start, stop, step = key, key + 1, 1
+        elif isinstance(key, slice):
+            start, stop, step = key.indices(self.size)
+            if stop < start:
+                start, stop = stop, start
+        elif key == Ellipsis:
+            start, stop, step = 0, self.size, 1
         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 
+            raise TypeError("Buffer indices must be integers or strings")
 
+        # Buffer is a base buffer and we have CPU storage
+        if self.data is not None:
+            # WARNING: do we check data size
+            #          or do we let numpy raises an error ?
+            self.data[key] = data
+            offset = start  # * self.itemsize
+            self.set_subdata(data=self.data[start:stop],
+                             offset=offset, copy=False)
 
+        # Buffer is a base buffer but we do not have CPU storage
+        # If 'key' points to a contiguous chunk of buffer, it's ok
+        elif step == 1:
+            offset = start  # * self.itemsize
 
-# ------------------------------------------------------ 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(...)
+            # Make sure data is an array
+            if not isinstance(data, np.ndarray):
+                data = np.array(data, dtype=self.dtype, copy=False)
 
-    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,
-                    }
+            # Make sure data is big enough
+            if data.size != stop - start:
+                data = np.resize(data, stop - start)
 
-    
-    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))
-        
+            self.set_subdata(data=data, offset=offset, copy=True)
+
+        # All the above fails, we raise an error
         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 
+            raise ValueError(
+                "Cannot set non contiguous data on buffer without CPU storage")
 
 
+class DataBufferView(DataBuffer):
+    """ View on a sub-region of a DataBuffer.
 
-# ------------------------------------------------------ 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.
+    Parameters
+    ----------
+    base : DataBuffer
+        The buffer accessed by this view.
+    key : str, int, slice, or Ellpsis
+        The index into the base buffer that defines a sub-region of the buffer
+        to view. String arguments select a single field from multi-field 
+        dtypes, and other allowed types select a subset of rows. 
+        
+    Notes
+    -----
+    
+    It is gnerally not necessary to instantiate this class manually; use 
+    ``base_buffer[key]`` instead.
     """
-    
-    def __init__(self, dtype, base, offset):
-        """ Initialize the view """
-        assert isinstance(dtype, np.dtype)
-        VertexBuffer.__init__(self, dtype)
-        
+
+    def __init__(self, base, key):
         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__)
-    
-    
+        self._key = key
+        self._target = base.target
+        self._stride = base.stride
+
+        if isinstance(key, str):
+            self._dtype = base.dtype[key]
+            self._offset = base.dtype.fields[key][1]
+            self._nbytes = base.size * self._dtype.itemsize
+            self._size = base.size
+            self._itemsize = self._dtype.itemsize
+            return
+        
+        if isinstance(key, int):
+            if key < 0:
+                key += base.size
+            if key < 0 or key > base.size:
+                raise IndexError("Buffer assignment index out of range")
+            start, stop, step = key, key + 1, 1
+        elif isinstance(key, slice):
+            start, stop, step = key.indices(base.size)
+            if stop < start:
+                start, stop = stop, start
+        elif key == Ellipsis:
+            start, stop, step = 0, base.size, 1
+        else:
+            raise TypeError("Buffer indices must be integers or strings")
+
+        if step != 1:
+            raise ValueError("Cannot access non-contiguous data")
+
+        self._itemsize = base.itemsize
+        self._offset = start * self.itemsize
+        self._size = stop - start
+        self._dtype = base.dtype
+        self._nbytes = self.size * self.itemsize
+
     @property
     def handle(self):
-        # Handle on base buffer. (avoid showing up in docs)
-        self._handle = self._base._handle
-        return self._handle
-    
-    
+        """ Name of this object on the GPU """
+
+        return self._base.handle
+
+    @property
+    def target(self):
+        """ OpenGL type of object. """
+
+        return self._base.target
+
+    def activate(self):
+        """ Activate the object on GPU """
+
+        self._base.activate()
+
+    def deactivate(self):
+        """ Deactivate the object on GPU """
+
+        self._base.deactivate()
+
+    def set_data(self, data, copy=False):
+        raise ValueError("Cannot set_data on buffer view; only set_subdata is "
+                         "allowed.")
+
+    @property
+    def dtype(self):
+        """ Buffer dtype """
+
+        return self._dtype
+
+    @property
+    def offset(self):
+        """ Buffer offset (in bytes) relative to base """
+
+        return self._offset
+
     @property
     def stride(self):
-        """ Byte number separating two elements. """
-        self._stride = self._base.stride
+        """ Stride of data in memory """
+
         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. """
+        """Buffer base if this buffer is a view on another buffer. """
+
         return self._base
 
+    @property
+    def size(self):
+        """ Number of elements in the buffer """
+        return self._size
+
+    @property
+    def data(self):
+        """ Buffer CPU storage """
 
-    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()
+        return self.base.data
 
+    @property
+    def itemsize(self):
+        """ The total number of bytes required to store the array data """
 
-    def _deactivate(self):
-        """ Unbind the base buffer """
-        self._base.deactivate()
+        return self._itemsize
 
+    @property
+    def glsl_type(self):
+        """ GLSL declaration strings required for a variable to hold this data.
+        """
+        dtshape = self.dtype[0].shape
+        n = dtshape[0] if dtshape else 1
+        if n > 1:
+            dtype = 'vec%d' % n
+        else:
+            dtype = 'float' if 'f' in self.dtype[0].base.kind else 'int'
+        return 'attribute', dtype
 
-    def _update(self):
-        """ Update base buffer. """
-        pass  # base._update is called from base.activate
+    def resize_bytes(self, size):
+        raise TypeError("Cannot resize buffer view.")
 
+    def __getitem__(self, key):
+        """ Create a view on this buffer. """
 
+        raise ValueError("Can only access data from a base buffer")
 
+    def __setitem__(self, key, data):
+        """ Set data (deferred operation) """
+
+        if not self._valid:
+            raise ValueError("This buffer view has been invalidated")
+
+        if isinstance(key, str):
+            raise ValueError(
+                "Cannot set a specific field on a non-base buffer")
+
+        elif key == Ellipsis and self.base is not None:
+            # WARNING: do we check data size
+            #          or do we let numpy raises an error ?
+            self.base[self._key] = data
+            return
+        # Setting one or several elements
+        elif isinstance(key, int):
+            if key < 0:
+                key += self.size
+            if key < 0 or key > self.size:
+                raise IndexError("Buffer assignment index out of range")
+            start, stop, step = key, key + 1, 1
+        elif isinstance(key, slice):
+            start, stop, step = key.indices(self.size)
+            if stop < start:
+                start, stop = stop, start
+        elif key == Ellipsis:
+            start, stop = 0, self.size
+        else:
+            raise TypeError("Buffer indices must be integers or strings")
+
+        # Set data on base buffer
+        base = self.base
+        # Base buffer has CPU storage
+        if base.data is not None:
+            # WARNING: do we check data size
+            #          or do we let numpy raises an error ?
+            base.data[key] = data
+            offset = start * base.itemsize
+            data = base.data[start:stop]
+            base.set_subdata(data=data, offset=offset, copy=False)
+        # Base buffer has no CPU storage, we cannot do operation
+        else:
+            raise ValueError(
+                """Cannot set non contiguous data """
+                """on buffer without CPU storage""")
+
+    def __repr__(self):
+        return ("<DataBufferView on %r at offset=%d size=%d>" % 
+                (self.base, self.offset, self.size))
 
-# ------------------------------------------------ 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.
+# ------------------------------------------------------ VertexBuffer class ---
+class VertexBuffer(DataBuffer):
+    """ Buffer for vertex attribute data
+
+    Parameters
+    ----------
+
+    data : ndarray
+        Buffer data (optional)
+    dtype : dtype
+        Buffer data type (optional)
+    size : int
+        Buffer size (optional)
+    store : bool
+        Specify whether this object stores a reference to the data,
+        allowing the data to be updated regardless of striding. Note
+        that modifying the data after passing it here might result in
+        undesired behavior, unless a copy is given. Default True.
     """
-    
-    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
+
+    def __init__(self, data=None, dtype=None, size=0, store=True):
+
+        if isinstance(data, (list, tuple)):
+            data = np.array(data, np.float32)
+
+        if dtype is not None:
+            dtype = np.dtype(dtype)
+            if dtype.isbuiltin:
+                dtype = np.dtype([('f0', dtype, 1)])
+
+        DataBuffer.__init__(self, data=data, dtype=dtype, size=size,
+                            target=gl.GL_ARRAY_BUFFER,
+                            store=store)
+
+        # Check base type and count for each dtype fields (if buffer is a base)
+        for name in self.dtype.names:
+            btype = self.dtype[name].base
+            if len(self.dtype[name].shape):
+                count = 1
+                s = self.dtype[name].shape
+                for i in range(len(s)):
+                    count *= s[i]
+                #count = reduce(mul, self.dtype[name].shape)
+            else:
+                count = 1
+            if btype not in [np.int8,  np.uint8,  np.float16,
+                             np.int16, np.uint16, np.float32]:
+                msg = ("Data basetype %r not allowed for Buffer/%s" 
+                       % (btype, name))
+                raise TypeError(msg)
+            elif count not in [1, 2, 3, 4]:
+                msg = ("Data basecount %s not allowed for Buffer/%s"
+                       % (count, name))
+                raise TypeError(msg)
+
+    def _prepare_data(self, data, convert=False):
+        # Build a structured view of the data if:
+        #  -> it is not already a structured array
+        #  -> shape if 1-D or last dimension is 1,2,3 or 4
+        if data.dtype.isbuiltin:
+            if convert is True and data.dtype is not np.float32:
+                data = data.astype(np.float32)
+            c = data.shape[-1]
+            if data.ndim == 1 or (data.ndim == 2 and c == 1):
+                data.shape = (data.size,)  # necessary in case (N,1) array
+                data = data.view(dtype=[('f0', data.dtype.base, 1)])
+            elif c in [1, 2, 3, 4]:
+                if not data.flags['C_CONTIGUOUS']:
+                    logger.warning("Copying discontiguous data for struct "
+                                   "dtype")
+                    data = data.copy()
+                data = data.view(dtype=[('f0', data.dtype.base, c)])
+            else:
+                data = data.view(dtype=[('f0', data.dtype.base, 1)])
+        return data
 
 
+# ------------------------------------------------------- IndexBuffer class ---
+class IndexBuffer(DataBuffer):
+    """ Buffer for index data
 
-# ----------------------------------------------- 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.
+    Parameters
+    ----------
+
+    data : ndarray
+        Buffer data (optional)
+    dtype : dtype
+        Buffer data type (optional)
+    size : int
+        Buffer size (optional)
+    store : bool
+        Specify whether this object stores a reference to the data,
+        allowing the data to be updated regardless of striding. Note
+        that modifying the data after passing it here might result in
+        undesired behavior, unless a copy is given. Default True.
     """
-    
-    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
 
+    def __init__(self, data=None, dtype=np.uint32, size=0, store=True):
+
+        if dtype and not np.dtype(dtype).isbuiltin:
+            raise TypeError("Element buffer dtype cannot be structured")
+
+        if isinstance(data, np.ndarray):
+            pass
+        elif dtype not in [np.uint8, np.uint16, np.uint32]:
+            raise TypeError("Data type not allowed for IndexBuffer")
+
+        DataBuffer.__init__(self, data=data, dtype=dtype, size=size,
+                            target=gl.GL_ELEMENT_ARRAY_BUFFER,
+                            store=store)
 
+    def _prepare_data(self, data, convert=False):
+        if not data.dtype.isbuiltin:
+            raise TypeError("Element buffer dtype cannot be structured")
+        else:
+            if convert is True and data.dtype is not np.uint32:
+                data = data.astype(np.uint32)
+        return data
diff --git a/vispy/gloo/framebuffer.py b/vispy/gloo/framebuffer.py
index c232b94..729a7f2 100644
--- a/vispy/gloo/framebuffer.py
+++ b/vispy/gloo/framebuffer.py
@@ -1,351 +1,377 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
 # 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
-
+#import OpenGL.GL as gl
 from . import gl
-from . import GLObject, ext_available, convert_to_enum
-from . import Texture2D
+from .globject import GLObject
+from .texture import Texture2D
+from ..util import logger
 
-# todo: check and test all _delete methods
+# ------------------------------------------------------ RenderBuffer class ---
 
-# 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 RenderBuffer(GLObject):
+    """ Base class for render buffer object
 
-class FrameBufferError(RuntimeError):
-    """ Raised when something goes wrong that depens on state that was set 
-    earlier (due to deferred loading).
+    Parameters
+    ----------
+    format : GLEnum
+        The buffer format: gl.GL_RGB565, gl.GL_RGBA4, gl.GL_RGB5_A1,
+        gl.GL_DEPTH_COMPONENT16, or gl.GL_STENCIL_INDEX8
+    shape : tuple of 2 ints
+        Buffer shape (always two dimensional)
+    resizeable : bool
+        Indicates whether texture can be resized
     """
-    pass
 
+    def __init__(self, shape=None, format=None, resizeable=True):
+        GLObject.__init__(self)
+        self._shape = shape
+        self._target = gl.GL_RENDERBUFFER
+        self._format = format
+        self._resizeable = resizeable
+        self._need_resize = True
 
+    @property
+    def shape(self):
+        """ Buffer shape """
+
+        return self._shape
+
+    def resize(self, shape):
+        """ Resize the buffer (deferred operation)
 
-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.
-        
+
+        shape : tuple of 2 integers
+            New buffer shape (always two dimensional)
         """
-        # 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
+
+        if not self._resizeable:
+            raise RuntimeError("Buffer is not resizeable")
+
+        if len(shape) != len(self.shape):
+            raise ValueError("New shape has wrong number of dimensions")
+
+        if shape == self.shape:
+            return
+
+        self._need_resize = True
         self._shape = shape
-        self._format = format or self._format
-        self._need_update = True
-    
-    
+
     def _create(self):
-        self._handle = gl.glGenRenderbuffers(1)
-    
-    
+        """ Create buffer on GPU """
+
+        logger.debug("GPU: Create render buffer")
+        self._handle = gl.glCreateRenderbuffer()
+
     def _delete(self):
-       gl.glDeleteRenderbuffers([self._handle])
-    
-    
+        """ Delete buffer from GPU """
+
+        logger.debug("GPU: Deleting render buffer")
+        gl.glDeleteRenderbuffer(self._handle)
+
     def _activate(self):
+        """ Activate buffer on GPU """
+
+        logger.debug("GPU: Activate render buffer")
         gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._handle)
-    
-    
+        
+        # Resize if necessary
+        if self._need_resize:
+            self._resize()
+            self._need_resize = False
+
     def _deactivate(self):
+        """ Deactivate buffer on GPU """
+
+        logger.debug("GPU: Deactivate render buffer")
         gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, 0)
+
+    def _resize(self):
+        """ Buffer resize on GPU """
+
+        # WARNING: Shape should be checked against maximum size
+        # maxsize = gl.glGetParameter(gl.GL_MAX_RENDERBUFFER_SIZE)
+        logger.debug("GPU: Resize render buffer")
+        gl.glRenderbufferStorage(self._target, self._format,
+                                 self._shape[1], self._shape[0])
+
+
+# ------------------------------------------------------- ColorBuffer class ---
+class ColorBuffer(RenderBuffer):
+    """ Color buffer object
     
+    Parameters
+    ----------
+
+    format : GLEnum
+        gl.GL_RGB565, gl.GL_RGBA4, gl.GL_RGB5_A1
+    shape : tuple of 2 integers
+        Buffer shape (always two dimensional)
+    resizeable : bool
+        Indicates whether buffer can be resized
+    """
+
+    def __init__(self, shape, format=gl.GL_RGBA, resizeable=True):
+        # if format not in (gl.GL_RGB565, gl.GL_RGBA4, gl.GL_RGB5_A1):
+        #     raise ValueError("Format not allowed for color buffer")
+        RenderBuffer.__init__(self, shape, format, resizeable)
+
+
+# ------------------------------------------------------- DepthBuffer class ---
+class DepthBuffer(RenderBuffer):
+    """ Depth buffer object
     
-    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])
+    Parameters
+    ----------
+
+    shape : tuple of 2 integers
+        Buffer shape (always two dimensional)
+    format : GLEnum
+        gl.GL_DEPTH_COMPONENT16
+    resizeable : bool
+        Indicates whether buffer can be resized
+    """
+
+    def __init__(self, shape,
+                 format=gl.GL_DEPTH_COMPONENT, resizeable=True):
+        #if format not in (gl.GL_DEPTH_COMPONENT16,):
+        #    raise ValueError("Format not allowed for depth buffer")
+        RenderBuffer.__init__(self, shape, format, resizeable)
+
+
+# ----------------------------------------------------- StencilBuffer class ---
+class StencilBuffer(RenderBuffer):
+    """ Stencil buffer object
     
+    Parameters
+    ----------
 
+    shape : tuple of 2 integers
+        Buffer shape (always two dimensional)
+    format : GLEnum
+        gl.GL_STENCIL_INDEX8
+    resizeable : bool
+        Indicates whether buffer can be resized
+    """
 
+    def __init__(self, shape,
+                 format=gl.GL_STENCIL_INDEX8, resizeable=True):
+        # if format not in (gl.GL_STENCIL_INDEX,):
+        #     raise ValueError("Format not allowed for color buffer")
+        RenderBuffer.__init__(self, shape, format, resizeable)
 
+
+# ------------------------------------------------------- FrameBuffer class ---
 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.
+    """ Frame buffer object
+    
+    Parameters
+    ----------
+    
+    color : ColorBuffer (optional)
+        The color buffer to attach to this frame buffer
+    depth : DepthBuffer (optional)
+        The depth buffer to attach to this frame buffer
+    stencil : StencilBuffer (optional)
+        The stencil buffer to attach to this frame buffer
+    resizable : bool
+        Whether the buffers are resizable (default True)
     """
-    
-    def __init__(self, color=None, depth=None, stencil=None):
+
+    def __init__(self, color=None, depth=None, stencil=None, resizeable=True):
+        """
+        """
+
         GLObject.__init__(self)
-        
-        # Init pending attachments
+
+        self._shape = None
+        self._color_buffer = color
+        self._depth_buffer = depth
+        self._stencil_buffer = stencil
+        self._need_attach = True
+        self._resizeable = resizeable
         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
-    
-    
+            self.color_buffer = color
+        if depth is not None:
+            self.depth_buffer = depth
+        if stencil is not None:
+            self.stencil_buffer = stencil
+
+    def __enter__(self):
+        self.activate()
+        return self
+
+    def __exit__(self, t, val, trace):
+        self.deactivate()
+
     @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) )
+    def color_buffer(self):
+        """Color buffer attachment"""
+
+        return self._color_buffer
+
+    @color_buffer.setter
+    def color_buffer(self, buffer):
+        """Color buffer attachment"""
+
+        target = gl.GL_COLOR_ATTACHMENT0
+        if isinstance(buffer, (ColorBuffer, Texture2D)) or buffer is None:
+            self._color_buffer = buffer
+            self._pending_attachments.append((target, buffer))
+            self._need_attach = True
         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) )
+            raise TypeError("Buffer must be a ColorBuffer, Texture2D or None."
+                            " (got %s)" % type(buffer))
+
+    @property
+    def depth_buffer(self):
+        """Depth buffer attachment"""
+
+        return self._depth_buffer
+
+    @depth_buffer.setter
+    def depth_buffer(self, buffer):
+        """Depth buffer attachment"""
+
+        target = gl.GL_DEPTH_ATTACHMENT
+        if isinstance(buffer, (DepthBuffer, Texture2D)) or buffer is None:
+            self._depth_buffer = buffer
+            self._pending_attachments.append((target, buffer))
+            self._need_attach = True
         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) )
+            raise TypeError("Buffer must be a DepthBuffer, Texture2D or None."
+                            " (got %s)" % type(buffer))
+
+    @property
+    def stencil_buffer(self):
+        """Stencil buffer attachment"""
+
+        return self._stencil_buffer
+
+    @stencil_buffer.setter
+    def stencil_buffer(self, buffer):
+        """Stencil buffer attachment"""
+
+        target = gl.GL_STENCIL_ATTACHMENT
+        if isinstance(buffer, StencilBuffer) or buffer is None:
+            self._stencil_buffer = buffer
+            self._pending_attachments.append((target, buffer))
+            self._need_attach = True
         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.
+            raise TypeError("Buffer must be a StencilBuffer, Texture2D or "
+                            "None. (got %s)" % type(buffer))
+
+    @property
+    def shape(self):
+        """ Buffer shape """
+
+        return self._shape
+
+    def resize(self, shape):
+        """ Resize the buffer (deferred operation)
+
+        Parameters
+        ----------
+
+        shape : tuple of 2 integers
+            New buffer shape (always two dimensional)
         """
-        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)
-    
-    
+
+        if not self._resizeable:
+            raise RuntimeError("FrameBuffer is not resizeable")
+
+        if len(shape) != 2:
+            raise ValueError("New shape has wrong number of dimensions")
+
+        if self.color_buffer is not None:
+            self.color_buffer.resize(shape)
+        if self.depth_buffer is not None:
+            self.depth_buffer.resize(shape)
+        if self.stencil_buffer is not None:
+            self.stencil_buffer.resize(shape)
+
     def _create(self):
-        self._handle = gl.glGenFramebuffers(1)
-    
-    
+        """ Create framebuffer on GPU """
+
+        logger.debug("GPU: Create framebuffer")
+        self._handle = gl.glCreateFramebuffer()
+
     def _delete(self):
-       gl.glDeleteFramebuffers([self._handle])
-    
-    
+        """ Delete buffer from GPU """
+
+        logger.debug("GPU: Delete framebuffer")
+        gl.glDeleteFramebuffer(self._handle)
+
     def _activate(self):
+        """ Activate framebuffer on GPU """
+
+        logger.debug("GPU: Activate render framebuffer")
         gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._handle)
-    
+        
+        # Attach buffers if necessary
+        if self._need_attach:
+            self._attach()
+            self._need_attach = False
     
     def _deactivate(self):
+        """ Deactivate framebuffer on GPU """
+
+        logger.debug("GPU: Deactivate render framebuffer")
         gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
-    
-    
-    def _update(self):
+
+    def _attach(self):
+        """ Attach render buffers to framebuffer """
         
-        # We need to activate before we can add attachements
-        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._handle)
+        # todo: this can currently only attach to texture mipmap level 0
         
-        # 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.
+        logger.debug("GPU: Attach render buffers")
         while self._pending_attachments:
-            attachment, object, level = self._pending_attachments.pop(0)
-            if object == 0:
+            attachment, buffer = self._pending_attachments.pop(0)
+            if buffer is None:
+                gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, attachment,
+                                             gl.GL_RENDERBUFFER, 0)
+            elif isinstance(buffer, RenderBuffer):
+                buffer.activate()
                 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)
+                                             gl.GL_RENDERBUFFER, buffer.handle)
+                buffer.deactivate()
+            elif isinstance(buffer, Texture2D):
+                buffer.activate()
+                # INFO: 0 is for mipmap level 0 (default) of the texture
+                gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, attachment,
+                                          buffer.target, buffer.handle, 0)
+                buffer.deactivate()
             else:
-                raise FrameBufferError('Invalid attachment. This should not happen.')
-        
-        # Check
-        if True:
+                raise ValueError("Invalid attachment: %s" % type(buffer))
+
+        if 1:
             res = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
             if res == gl.GL_FRAMEBUFFER_COMPLETE:
                 pass
             elif res == 0:
-                raise FrameBufferError('Target not equal to GL_FRAMEBUFFER')
+                raise RuntimeError('Target not equal to GL_FRAMEBUFFER')
             elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
-                raise FrameBufferError('FrameBuffer attachments are incomplete.')
+                raise RuntimeError(
+                    'FrameBuffer attachments are incomplete.')
             elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
-                raise FrameBufferError('No valid attachments in the FrameBuffer.')
+                raise RuntimeError(
+                    '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.')
+                raise RuntimeError(
+                    'attachments do not have the same width and height.')
+            #elif res == gl.GL_FRAMEBUFFER_INCOMPLETE_FORMATS: # not in es 2.0
+            #    raise RuntimeError('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.')
-    
-
+                raise RuntimeError('Combination of internal formats used '
+                                   'by attachments is not supported.')
+            else:
+                raise RuntimeError('Unknown framebuffer error: %r.' % res)
diff --git a/vispy/gloo/gl/__init__.py b/vispy/gloo/gl/__init__.py
index 6572ef2..e57b22a 100644
--- a/vispy/gloo/gl/__init__.py
+++ b/vispy/gloo/gl/__init__.py
@@ -1,76 +1,188 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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.
+""" This module provides a (functional) API to OpenGL ES 2.0.
+
+There are multiple backend implementations of this API, available as
+submodules of this module. One can use one of the backends directly,
+or call `gl.use_gl()` to select one. The backend system allow running
+visualizations using Angle, WebGL, or other forms of remote rendering.
+This is in part possible by the widespread availability of OpenGL ES 2.0.
+
+All functions that this API provides accept and return Python arguments
+(no ctypes is required); strings are real strings and you can pass 
+data as numpy arrays. In general the input arguments are not checked
+(for performance reasons). Each function results in exactly one OpenGL
+API call, except when using the pyopengl backend.
+
+The functions do not have docstrings, but most IDE's should provide you
+with the function signature. For more documentation see
+http://www.khronos.org/opengles/sdk/docs/man/
+
 """
 
-# NOTE: modules in this package that start with one underscore are autogenerated
+# NOTE: modules in this package that start with one underscore are
+# autogenerated, and should not be edited.
 
-from __future__ import print_function, division, absolute_import
+from __future__ import division
 
-import vispy
+from ...util import config, logger
 
+from ._constants import *  # noqa
+from ._proxy import BaseGLProxy
 
-class _GL_ENUM(int):
-    """ Type to represent OpenGL constants.
+
+# Variable that will hold the module corresponding to the current backend
+# This variable is used in our proxy classes to call the right functions.
+current_backend = None
+
+
+class MainProxy(BaseGLProxy):
+    """ Main proxy for the GL ES 2.0 API. 
+    
+    The functions in this namespace always call into the correct GL
+    backend. Therefore these function objects can be safely stored for
+    reuse. However, for efficienty it would probably be better to store the
+    function name and then do ``func = getattr(gloo.gl, funcname)``.
     """
-    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))
+    
+    def __call__(self, funcname, returns, *args):
+        func = getattr(current_backend, funcname)
+        return func(*args)
+
+
+class DebugProxy(BaseGLProxy):
+    """ Proxy for debug version of the GL ES 2.0 API. 
+    
+    This proxy calls the functions of the currently selected backend.
+    In addition it logs debug information, and it runs check_error()
+    on each API call. Intended for internal use.
+    """
+    
+    def _arg_repr(self, arg):
+        """ Get a useful (and not too large) represetation of an argument.
+        """
+        r = repr(arg)
+        max = 40
+        if len(r) > max:
+            if hasattr(arg, 'shape'):
+                r = 'array:' + 'x'.join([repr(s) for s in arg.shape])
+            else:
+                r = r[:max-3] + '...'
+        return r
+    
+    def __call__(self, funcname, returns, *args):
+        # Avoid recursion for glGetError
+        if funcname == 'glGetError':
+            func = getattr(current_backend, funcname)
+            return func()
+        # Log function call
+        argstr = ', '.join(map(self._arg_repr, args))
+        logger.debug("%s(%s)" % (funcname, argstr))
+        # Call function
+        func = getattr(current_backend, funcname)
+        ret = func(*args)
+        # Log return value
+        if returns:
+            logger.debug(" <= %s" % repr(ret))
+        # Check for errors (raises if an error occured)
+        check_error(funcname)
+        # Return
         return ret
-    return cb
 
 
+# Instantiate proxy objects
+proxy = MainProxy()
+_debug_proxy = DebugProxy()
+
+
+def use_gl(target='desktop'):
+    """ Let Vispy use the target OpenGL ES 2.0 implementation
+    
+    Also see ``vispy.use()``.
+    
+    Parameters
+    ----------
+    target : str
+        The target GL backend to use.
+
+    Available backends:
+    * desktop - Use desktop (i.e. normal) OpenGL.
+    * pyopengl - Use pyopengl (for fallback and testing). 
+    * angle - Use the Angle library to target DirectX (Windows only). (WIP)
+    * mock - Dummy backend that can be useful for testing. (not yet available)
+    * webgl - Send the GL commands to the browser. (not yet available)
 
-def use(target='desktop'):
-    """ Let Vispy use the target OpenGL ES 2.0 implementation.
-    Currently, only "desktop" is supported.
     """
-    debug = vispy.config['gl_debug']
+    target = target or 'desktop'
+
+    # Get options
+    target, _, options = target.partition(' ')
+    debug = config['gl_debug'] or ('debug' in options)
     
     # Select modules to import names from
-    if target == 'desktop':
-        from . import desktop as mod
+    try:
+        mod = __import__(target, globals(), level=1)
+    except ImportError as err:
+        msg = 'Could not import gl target "%s":\n%s' % (target, str(err))
+        raise RuntimeError(msg)
+
+    # Apply
+    global current_backend
+    current_backend = mod
+    if debug:
+        _copy_gl_functions(_debug_proxy, globals())
     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')]
+        _copy_gl_functions(mod, globals())
+
+
+def _copy_gl_functions(source, dest):
+    """ Inject all objects that start with 'gl' from the source
+    into the dest. source and dest can be dicts, modules or BaseGLProxy's.
+    """
+    # Get dicts
+    if isinstance(source, BaseGLProxy):
+        s = {}
+        for key in dir(source):
+            s[key] = getattr(source, key)
+        source = s
+    elif not isinstance(source, dict):
+        source = source.__dict__
+    if not isinstance(dest, dict):
+        dest = dest.__dict__
+    # Make selection of names
+    funcnames = [name for name in source.keys() if name.startswith('gl')]
     for name in funcnames:
-        func = getattr(mod.ext, name)
-        if debug:
-            func = _make_debug_wrapper(name, func)
-        NS[name] = func
+        dest[name] = source[name]
+
+
+def check_error(when='periodic check'):
+    """ Check this from time to time to detect GL errors.
+
+    Parameters
+    ----------
+    when : str
+        Shown in the exception to help the developer determine when
+        this check was done.
+    """
+
+    errors = []
+    while True:
+        err = glGetError()
+        if err == GL_NO_ERROR or (errors and err == errors[-1]):
+            break
+        errors.append(err)
+    if errors:
+        msg = ', '.join([repr(ENUM_MAP.get(e, e)) for e in errors])
+        err = RuntimeError('OpenGL got errors (%s): %s' % (when, msg))
+        err.errors = errors
+        err.err = errors[-1]  # pyopengl compat
+        raise err
 
 
-# Import ext namespace and constants
-from . import ext
-from ._constants import *
+# Load default gl backend
+from . import desktop as default_backend  # noqa
 
-# Fill this namespace with functions
-use()
+# Call use to start using our default backend
+use_gl()
diff --git a/vispy/gloo/gl/_angle.py b/vispy/gloo/gl/_angle.py
new file mode 100644
index 0000000..5e18868
--- /dev/null
+++ b/vispy/gloo/gl/_angle.py
@@ -0,0 +1,986 @@
+"""
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+GL ES 2.0 API based on the Angle library (i.e. DirectX)
+
+"""
+
+import ctypes
+from .angle import _lib
+
+
+_lib.glActiveTexture.argtypes = ctypes.c_uint,
+# void = glActiveTexture(GLenum texture)
+def glActiveTexture(texture):
+    _lib.glActiveTexture(texture)
+
+
+_lib.glAttachShader.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glAttachShader(GLuint program, GLuint shader)
+def glAttachShader(program, shader):
+    _lib.glAttachShader(program, shader)
+
+
+_lib.glBindAttribLocation.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_char_p,
+# void = glBindAttribLocation(GLuint program, GLuint index, GLchar* name)
+def glBindAttribLocation(program, index, name):
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    res = _lib.glBindAttribLocation(program, index, name)
+
+
+_lib.glBindBuffer.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glBindBuffer(GLenum target, GLuint buffer)
+def glBindBuffer(target, buffer):
+    _lib.glBindBuffer(target, buffer)
+
+
+_lib.glBindFramebuffer.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glBindFramebuffer(GLenum target, GLuint framebuffer)
+def glBindFramebuffer(target, framebuffer):
+    _lib.glBindFramebuffer(target, framebuffer)
+
+
+_lib.glBindRenderbuffer.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glBindRenderbuffer(GLenum target, GLuint renderbuffer)
+def glBindRenderbuffer(target, renderbuffer):
+    _lib.glBindRenderbuffer(target, renderbuffer)
+
+
+_lib.glBindTexture.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glBindTexture(GLenum target, GLuint texture)
+def glBindTexture(target, texture):
+    _lib.glBindTexture(target, texture)
+
+
+_lib.glBlendColor.argtypes = ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+# void = glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
+def glBlendColor(red, green, blue, alpha):
+    _lib.glBlendColor(red, green, blue, alpha)
+
+
+_lib.glBlendEquation.argtypes = ctypes.c_uint,
+# void = glBlendEquation(GLenum mode)
+def glBlendEquation(mode):
+    _lib.glBlendEquation(mode)
+
+
+_lib.glBlendEquationSeparate.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha)
+def glBlendEquationSeparate(modeRGB, modeAlpha):
+    _lib.glBlendEquationSeparate(modeRGB, modeAlpha)
+
+
+_lib.glBlendFunc.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glBlendFunc(GLenum sfactor, GLenum dfactor)
+def glBlendFunc(sfactor, dfactor):
+    _lib.glBlendFunc(sfactor, dfactor)
+
+
+_lib.glBlendFuncSeparate.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,
+# void = glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha)
+def glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha):
+    _lib.glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha)
+
+
+_lib.glBufferData.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint,
+# void = glBufferData(GLenum target, GLsizeiptr size, GLvoid* data, GLenum usage)
+def glBufferData(target, data, usage):
+    """ Data can be numpy array or the size of data to allocate.
+    """
+    if isinstance(data, int):
+        size = data
+        data = ctypes.c_voidp(0)
+    else:
+        if not data.flags['C_CONTIGUOUS'] or not data.flags['ALIGNED']:
+            data = data.copy('C')
+        data_ = data
+        size = data_.nbytes
+        data = data_.ctypes.data
+    res = _lib.glBufferData(target, size, data, usage)
+
+
+_lib.glBufferSubData.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_void_p,
+# void = glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data)
+def glBufferSubData(target, offset, data):
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.nbytes
+    data = data_.ctypes.data
+    res = _lib.glBufferSubData(target, offset, size, data)
+
+
+_lib.glCheckFramebufferStatus.argtypes = ctypes.c_uint,
+_lib.glCheckFramebufferStatus.restype = ctypes.c_uint
+# GLenum = glCheckFramebufferStatus(GLenum target)
+def glCheckFramebufferStatus(target):
+    return _lib.glCheckFramebufferStatus(target)
+
+
+_lib.glClear.argtypes = ctypes.c_uint,
+# void = glClear(GLbitfield mask)
+def glClear(mask):
+    _lib.glClear(mask)
+
+
+_lib.glClearColor.argtypes = ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+# void = glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
+def glClearColor(red, green, blue, alpha):
+    _lib.glClearColor(red, green, blue, alpha)
+
+
+_lib.glClearDepthf.argtypes = ctypes.c_float,
+# void = glClearDepthf(GLclampf depth)
+def glClearDepth(depth):
+    _lib.glClearDepthf(depth)
+
+
+_lib.glClearStencil.argtypes = ctypes.c_int,
+# void = glClearStencil(GLint s)
+def glClearStencil(s):
+    _lib.glClearStencil(s)
+
+
+_lib.glColorMask.argtypes = ctypes.c_bool, ctypes.c_bool, ctypes.c_bool, ctypes.c_bool,
+# void = glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)
+def glColorMask(red, green, blue, alpha):
+    _lib.glColorMask(red, green, blue, alpha)
+
+
+_lib.glCompileShader.argtypes = ctypes.c_uint,
+# void = glCompileShader(GLuint shader)
+def glCompileShader(shader):
+    _lib.glCompileShader(shader)
+
+
+_lib.glCompressedTexImage2D.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_void_p,
+# void = glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, GLvoid* data)
+def glCompressedTexImage2D(target, level, internalformat, width, height, border, data):
+    # border = 0  # set in args
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.size
+    data = data_.ctypes.data
+    res = _lib.glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data)
+
+
+_lib.glCompressedTexSubImage2D.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_void_p,
+# void = glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, GLvoid* data)
+def glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, data):
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.size
+    data = data_.ctypes.data
+    res = _lib.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data)
+
+
+_lib.glCopyTexImage2D.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
+# void = glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)
+def glCopyTexImage2D(target, level, internalformat, x, y, width, height, border):
+    _lib.glCopyTexImage2D(target, level, internalformat, x, y, width, height, border)
+
+
+_lib.glCopyTexSubImage2D.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
+# void = glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
+def glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height):
+    _lib.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height)
+
+
+_lib.glCreateProgram.argtypes = ()
+_lib.glCreateProgram.restype = ctypes.c_uint
+# GLuint = glCreateProgram()
+def glCreateProgram():
+    return _lib.glCreateProgram()
+
+
+_lib.glCreateShader.argtypes = ctypes.c_uint,
+_lib.glCreateShader.restype = ctypes.c_uint
+# GLuint = glCreateShader(GLenum type)
+def glCreateShader(type):
+    return _lib.glCreateShader(type)
+
+
+_lib.glCullFace.argtypes = ctypes.c_uint,
+# void = glCullFace(GLenum mode)
+def glCullFace(mode):
+    _lib.glCullFace(mode)
+
+
+_lib.glDeleteBuffers.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glDeleteBuffers(GLsizei n, GLuint* buffers)
+def glDeleteBuffer(buffer):
+    n = 1
+    buffers = (ctypes.c_uint*n)(buffer)
+    res = _lib.glDeleteBuffers(n, buffers)
+
+
+_lib.glDeleteFramebuffers.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glDeleteFramebuffers(GLsizei n, GLuint* framebuffers)
+def glDeleteFramebuffer(framebuffer):
+    n = 1
+    framebuffers = (ctypes.c_uint*n)(framebuffer)
+    res = _lib.glDeleteFramebuffers(n, framebuffers)
+
+
+_lib.glDeleteProgram.argtypes = ctypes.c_uint,
+# void = glDeleteProgram(GLuint program)
+def glDeleteProgram(program):
+    _lib.glDeleteProgram(program)
+
+
+_lib.glDeleteRenderbuffers.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glDeleteRenderbuffers(GLsizei n, GLuint* renderbuffers)
+def glDeleteRenderbuffer(renderbuffer):
+    n = 1
+    renderbuffers = (ctypes.c_uint*n)(renderbuffer)
+    res = _lib.glDeleteRenderbuffers(n, renderbuffers)
+
+
+_lib.glDeleteShader.argtypes = ctypes.c_uint,
+# void = glDeleteShader(GLuint shader)
+def glDeleteShader(shader):
+    _lib.glDeleteShader(shader)
+
+
+_lib.glDeleteTextures.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glDeleteTextures(GLsizei n, GLuint* textures)
+def glDeleteTexture(texture):
+    n = 1
+    textures = (ctypes.c_uint*n)(texture)
+    res = _lib.glDeleteTextures(n, textures)
+
+
+_lib.glDepthFunc.argtypes = ctypes.c_uint,
+# void = glDepthFunc(GLenum func)
+def glDepthFunc(func):
+    _lib.glDepthFunc(func)
+
+
+_lib.glDepthMask.argtypes = ctypes.c_bool,
+# void = glDepthMask(GLboolean flag)
+def glDepthMask(flag):
+    _lib.glDepthMask(flag)
+
+
+_lib.glDepthRangef.argtypes = ctypes.c_float, ctypes.c_float,
+# void = glDepthRangef(GLclampf zNear, GLclampf zFar)
+def glDepthRange(zNear, zFar):
+    _lib.glDepthRangef(zNear, zFar)
+
+
+_lib.glDetachShader.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glDetachShader(GLuint program, GLuint shader)
+def glDetachShader(program, shader):
+    _lib.glDetachShader(program, shader)
+
+
+_lib.glDisable.argtypes = ctypes.c_uint,
+# void = glDisable(GLenum cap)
+def glDisable(cap):
+    _lib.glDisable(cap)
+
+
+_lib.glDisableVertexAttribArray.argtypes = ctypes.c_uint,
+# void = glDisableVertexAttribArray(GLuint index)
+def glDisableVertexAttribArray(index):
+    _lib.glDisableVertexAttribArray(index)
+
+
+_lib.glDrawArrays.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_int,
+# void = glDrawArrays(GLenum mode, GLint first, GLsizei count)
+def glDrawArrays(mode, first, count):
+    _lib.glDrawArrays(mode, first, count)
+
+
+_lib.glDrawElements.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_void_p,
+# void = glDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid* indices)
+def glDrawElements(mode, count, type, offset):
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, ctypes.c_void_p):
+        pass
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    else:
+        if not offset.flags['C_CONTIGUOUS']:
+            offset = offset.copy('C')
+        offset_ = offset
+        offset = offset.ctypes.data
+    indices = offset
+    res = _lib.glDrawElements(mode, count, type, indices)
+
+
+_lib.glEnable.argtypes = ctypes.c_uint,
+# void = glEnable(GLenum cap)
+def glEnable(cap):
+    _lib.glEnable(cap)
+
+
+_lib.glEnableVertexAttribArray.argtypes = ctypes.c_uint,
+# void = glEnableVertexAttribArray(GLuint index)
+def glEnableVertexAttribArray(index):
+    _lib.glEnableVertexAttribArray(index)
+
+
+_lib.glFinish.argtypes = ()
+# void = glFinish()
+def glFinish():
+    _lib.glFinish()
+
+
+_lib.glFlush.argtypes = ()
+# void = glFlush()
+def glFlush():
+    _lib.glFlush()
+
+
+_lib.glFramebufferRenderbuffer.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,
+# void = glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
+def glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer):
+    _lib.glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer)
+
+
+_lib.glFramebufferTexture2D.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_int,
+# void = glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)
+def glFramebufferTexture2D(target, attachment, textarget, texture, level):
+    _lib.glFramebufferTexture2D(target, attachment, textarget, texture, level)
+
+
+_lib.glFrontFace.argtypes = ctypes.c_uint,
+# void = glFrontFace(GLenum mode)
+def glFrontFace(mode):
+    _lib.glFrontFace(mode)
+
+
+_lib.glGenBuffers.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glGenBuffers(GLsizei n, GLuint* buffers)
+def glCreateBuffer():
+    n = 1
+    buffers = (ctypes.c_uint*n)()
+    res = _lib.glGenBuffers(n, buffers)
+    return buffers[0]
+
+
+_lib.glGenFramebuffers.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glGenFramebuffers(GLsizei n, GLuint* framebuffers)
+def glCreateFramebuffer():
+    n = 1
+    framebuffers = (ctypes.c_uint*n)()
+    res = _lib.glGenFramebuffers(n, framebuffers)
+    return framebuffers[0]
+
+
+_lib.glGenRenderbuffers.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glGenRenderbuffers(GLsizei n, GLuint* renderbuffers)
+def glCreateRenderbuffer():
+    n = 1
+    renderbuffers = (ctypes.c_uint*n)()
+    res = _lib.glGenRenderbuffers(n, renderbuffers)
+    return renderbuffers[0]
+
+
+_lib.glGenTextures.argtypes = ctypes.c_int, ctypes.POINTER(ctypes.c_uint),
+# void = glGenTextures(GLsizei n, GLuint* textures)
+def glCreateTexture():
+    n = 1
+    textures = (ctypes.c_uint*n)()
+    res = _lib.glGenTextures(n, textures)
+    return textures[0]
+
+
+_lib.glGenerateMipmap.argtypes = ctypes.c_uint,
+# void = glGenerateMipmap(GLenum target)
+def glGenerateMipmap(target):
+    _lib.glGenerateMipmap(target)
+
+
+_lib.glGetActiveAttrib.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint), ctypes.c_char_p,
+# void = glGetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, GLchar* name)
+def glGetActiveAttrib(program, index):
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    res = _lib.glGetActiveAttrib(program, index, bufsize, length, size, type, name)
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+
+
+_lib.glGetActiveUniform.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint), ctypes.c_char_p,
+# void = glGetActiveUniform(GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, GLchar* name)
+def glGetActiveUniform(program, index):
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    res = _lib.glGetActiveUniform(program, index, bufsize, length, size, type, name)
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+
+
+_lib.glGetAttachedShaders.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint),
+# void = glGetAttachedShaders(GLuint program, GLsizei maxcount, GLsizei* count, GLuint* shaders)
+def glGetAttachedShaders(program):
+    maxcount = 256
+    count = (ctypes.c_int*1)()
+    shaders = (ctypes.c_uint*maxcount)()
+    res = _lib.glGetAttachedShaders(program, maxcount, count, shaders)
+    return tuple(shaders[:count[0]])
+
+
+_lib.glGetAttribLocation.argtypes = ctypes.c_uint, ctypes.c_char_p,
+_lib.glGetAttribLocation.restype = ctypes.c_int
+# GLint = glGetAttribLocation(GLuint program, GLchar* name)
+def glGetAttribLocation(program, name):
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    res = _lib.glGetAttribLocation(program, name)
+    return res
+
+
+_lib.glGetBooleanv.argtypes = ctypes.c_uint, ctypes.POINTER(ctypes.c_bool),
+# void = glGetBooleanv(GLenum pname, GLboolean* params)
+def _glGetBooleanv(pname):
+    params = (ctypes.c_bool*1)()
+    res = _lib.glGetBooleanv(pname, params)
+    return params[0]
+
+
+_lib.glGetBufferParameteriv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),
+# void = glGetBufferParameteriv(GLenum target, GLenum pname, GLint* params)
+def glGetBufferParameter(target, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    res = _lib.glGetBufferParameteriv(target, pname, params)
+    return params[0]
+
+
+_lib.glGetError.argtypes = ()
+_lib.glGetError.restype = ctypes.c_uint
+# GLenum = glGetError()
+def glGetError():
+    return _lib.glGetError()
+
+
+_lib.glGetFloatv.argtypes = ctypes.c_uint, ctypes.POINTER(ctypes.c_float),
+# void = glGetFloatv(GLenum pname, GLfloat* params)
+def _glGetFloatv(pname):
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    res = _lib.glGetFloatv(pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+_lib.glGetFramebufferAttachmentParameteriv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),
+# void = glGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params)
+def glGetFramebufferAttachmentParameter(target, attachment, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    res = _lib.glGetFramebufferAttachmentParameteriv(target, attachment, pname, params)
+    return params[0]
+
+
+_lib.glGetIntegerv.argtypes = ctypes.c_uint, ctypes.POINTER(ctypes.c_int),
+# void = glGetIntegerv(GLenum pname, GLint* params)
+def _glGetIntegerv(pname):
+    n = 16
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*n)(*[d for i in range(n)])
+    res = _lib.glGetIntegerv(pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+_lib.glGetProgramInfoLog.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p,
+# void = glGetProgramInfoLog(GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog)
+def glGetProgramInfoLog(program):
+    bufsize = 1024
+    length = (ctypes.c_int*1)()
+    infolog = ctypes.create_string_buffer(bufsize)
+    res = _lib.glGetProgramInfoLog(program, bufsize, length, infolog)
+    return infolog[:length[0]].decode('utf-8')
+
+
+_lib.glGetProgramiv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),
+# void = glGetProgramiv(GLuint program, GLenum pname, GLint* params)
+def glGetProgramParameter(program, pname):
+    params = (ctypes.c_int*1)()
+    res = _lib.glGetProgramiv(program, pname, params)
+    return params[0]
+
+
+_lib.glGetRenderbufferParameteriv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),
+# void = glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params)
+def glGetRenderbufferParameter(target, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    res = _lib.glGetRenderbufferParameteriv(target, pname, params)
+    return params[0]
+
+
+_lib.glGetShaderInfoLog.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p,
+# void = glGetShaderInfoLog(GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* infolog)
+def glGetShaderInfoLog(shader):
+    bufsize = 1024
+    length = (ctypes.c_int*1)()
+    infolog = ctypes.create_string_buffer(bufsize)
+    res = _lib.glGetShaderInfoLog(shader, bufsize, length, infolog)
+    return infolog[:length[0]].decode('utf-8')
+
+
+_lib.glGetShaderPrecisionFormat.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int),
+# void = glGetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision)
+def glGetShaderPrecisionFormat(shadertype, precisiontype):
+    range = (ctypes.c_int*1)()
+    precision = (ctypes.c_int*1)()
+    res = _lib.glGetShaderPrecisionFormat(shadertype, precisiontype, range, precision)
+    return range[0], precision[0]
+
+
+_lib.glGetShaderSource.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p,
+# void = glGetShaderSource(GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* source)
+def glGetShaderSource(shader):
+    bufsize = 1024*1024
+    length = (ctypes.c_int*1)()
+    source = (ctypes.c_char*bufsize)()
+    res = _lib.glGetShaderSource(shader, bufsize, length, source)
+    return source.value[:length[0]].decode('utf-8')
+
+
+_lib.glGetShaderiv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),
+# void = glGetShaderiv(GLuint shader, GLenum pname, GLint* params)
+def glGetShaderParameter(shader, pname):
+    params = (ctypes.c_int*1)()
+    res = _lib.glGetShaderiv(shader, pname, params)
+    return params[0]
+
+
+_lib.glGetString.argtypes = ctypes.c_uint,
+_lib.glGetString.restype = ctypes.c_char_p
+# GLubyte* = glGetString(GLenum name)
+def glGetParameter(pname):
+    if pname in [33902, 33901, 32773, 3106, 2931, 2928,
+                 2849, 32824, 10752, 32938]:
+        # GL_ALIASED_LINE_WIDTH_RANGE GL_ALIASED_POINT_SIZE_RANGE
+        # GL_BLEND_COLOR GL_COLOR_CLEAR_VALUE GL_DEPTH_CLEAR_VALUE
+        # GL_DEPTH_RANGE GL_LINE_WIDTH GL_POLYGON_OFFSET_FACTOR
+        # GL_POLYGON_OFFSET_UNITS GL_SAMPLE_COVERAGE_VALUE
+        return _glGetFloatv(pname)
+    elif pname in [7936, 7937, 7938, 35724, 7939]:
+        # GL_VENDOR, GL_RENDERER, GL_VERSION, GL_SHADING_LANGUAGE_VERSION,
+        # GL_EXTENSIONS are strings
+        pass  # string handled below
+    else:
+        return _glGetIntegerv(pname)
+    name = pname
+    res = _lib.glGetString(name)
+    return res.decode('utf-8') if res else ''
+
+
+_lib.glGetTexParameterfv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_float),
+# void = glGetTexParameterfv(GLenum target, GLenum pname, GLfloat* params)
+def glGetTexParameter(target, pname):
+    d = float('Inf')
+    params = (ctypes.c_float*1)(d)
+    res = _lib.glGetTexParameterfv(target, pname, params)
+    return params[0]
+
+
+_lib.glGetUniformfv.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_float),
+# void = glGetUniformfv(GLuint program, GLint location, GLfloat* params)
+def glGetUniform(program, location):
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    res = _lib.glGetUniformfv(program, location, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+_lib.glGetUniformLocation.argtypes = ctypes.c_uint, ctypes.c_char_p,
+_lib.glGetUniformLocation.restype = ctypes.c_int
+# GLint = glGetUniformLocation(GLuint program, GLchar* name)
+def glGetUniformLocation(program, name):
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    res = _lib.glGetUniformLocation(program, name)
+    return res
+
+
+_lib.glGetVertexAttribfv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_float),
+# void = glGetVertexAttribfv(GLuint index, GLenum pname, GLfloat* params)
+def glGetVertexAttrib(index, pname):
+    n = 4
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    res = _lib.glGetVertexAttribfv(index, pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+_lib.glGetVertexAttribPointerv.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_void_p),
+# void = glGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid** pointer)
+def glGetVertexAttribOffset(index, pname):
+    pointer = (ctypes.c_void_p*1)()
+    res = _lib.glGetVertexAttribPointerv(index, pname, pointer)
+    return pointer[0] or 0
+
+
+_lib.glHint.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glHint(GLenum target, GLenum mode)
+def glHint(target, mode):
+    _lib.glHint(target, mode)
+
+
+_lib.glIsBuffer.argtypes = ctypes.c_uint,
+_lib.glIsBuffer.restype = ctypes.c_bool
+# GLboolean = glIsBuffer(GLuint buffer)
+def glIsBuffer(buffer):
+    return _lib.glIsBuffer(buffer)
+
+
+_lib.glIsEnabled.argtypes = ctypes.c_uint,
+_lib.glIsEnabled.restype = ctypes.c_bool
+# GLboolean = glIsEnabled(GLenum cap)
+def glIsEnabled(cap):
+    return _lib.glIsEnabled(cap)
+
+
+_lib.glIsFramebuffer.argtypes = ctypes.c_uint,
+_lib.glIsFramebuffer.restype = ctypes.c_bool
+# GLboolean = glIsFramebuffer(GLuint framebuffer)
+def glIsFramebuffer(framebuffer):
+    return _lib.glIsFramebuffer(framebuffer)
+
+
+_lib.glIsProgram.argtypes = ctypes.c_uint,
+_lib.glIsProgram.restype = ctypes.c_bool
+# GLboolean = glIsProgram(GLuint program)
+def glIsProgram(program):
+    return _lib.glIsProgram(program)
+
+
+_lib.glIsRenderbuffer.argtypes = ctypes.c_uint,
+_lib.glIsRenderbuffer.restype = ctypes.c_bool
+# GLboolean = glIsRenderbuffer(GLuint renderbuffer)
+def glIsRenderbuffer(renderbuffer):
+    return _lib.glIsRenderbuffer(renderbuffer)
+
+
+_lib.glIsShader.argtypes = ctypes.c_uint,
+_lib.glIsShader.restype = ctypes.c_bool
+# GLboolean = glIsShader(GLuint shader)
+def glIsShader(shader):
+    return _lib.glIsShader(shader)
+
+
+_lib.glIsTexture.argtypes = ctypes.c_uint,
+_lib.glIsTexture.restype = ctypes.c_bool
+# GLboolean = glIsTexture(GLuint texture)
+def glIsTexture(texture):
+    return _lib.glIsTexture(texture)
+
+
+_lib.glLineWidth.argtypes = ctypes.c_float,
+# void = glLineWidth(GLfloat width)
+def glLineWidth(width):
+    _lib.glLineWidth(width)
+
+
+_lib.glLinkProgram.argtypes = ctypes.c_uint,
+# void = glLinkProgram(GLuint program)
+def glLinkProgram(program):
+    _lib.glLinkProgram(program)
+
+
+_lib.glPixelStorei.argtypes = ctypes.c_uint, ctypes.c_int,
+# void = glPixelStorei(GLenum pname, GLint param)
+def glPixelStorei(pname, param):
+    _lib.glPixelStorei(pname, param)
+
+
+_lib.glPolygonOffset.argtypes = ctypes.c_float, ctypes.c_float,
+# void = glPolygonOffset(GLfloat factor, GLfloat units)
+def glPolygonOffset(factor, units):
+    _lib.glPolygonOffset(factor, units)
+
+
+_lib.glReadPixels.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p,
+# void = glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
+def glReadPixels(x, y, width, height, format, type):
+    # GL_ALPHA, GL_RGB, GL_RGBA
+    t = {6406:1, 6407:3, 6408:4}[format]
+    # we kind of only support type GL_UNSIGNED_BYTE
+    size = int(width*height*t)
+    pixels = ctypes.create_string_buffer(size)
+    res = _lib.glReadPixels(x, y, width, height, format, type, pixels)
+    return pixels[:]
+
+
+_lib.glRenderbufferStorage.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.c_int,
+# void = glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
+def glRenderbufferStorage(target, internalformat, width, height):
+    _lib.glRenderbufferStorage(target, internalformat, width, height)
+
+
+_lib.glSampleCoverage.argtypes = ctypes.c_float, ctypes.c_bool,
+# void = glSampleCoverage(GLclampf value, GLboolean invert)
+def glSampleCoverage(value, invert):
+    _lib.glSampleCoverage(value, invert)
+
+
+_lib.glScissor.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
+# void = glScissor(GLint x, GLint y, GLsizei width, GLsizei height)
+def glScissor(x, y, width, height):
+    _lib.glScissor(x, y, width, height)
+
+
+_lib.glShaderSource.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_int),
+# void = glShaderSource(GLuint shader, GLsizei count, GLchar** string, GLint* length)
+def glShaderSource(shader, source):
+    # Some implementation do not like getting a list of single chars
+    if isinstance(source, (tuple, list)):
+        strings = [s for s in source]
+    else:
+        strings = [source]
+    count = len(strings)
+    string = (ctypes.c_char_p*count)(*[s.encode('utf-8') for s in strings])
+    length = (ctypes.c_int*count)(*[len(s) for s in strings])
+    res = _lib.glShaderSource(shader, count, string, length)
+
+
+_lib.glStencilFunc.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_uint,
+# void = glStencilFunc(GLenum func, GLint ref, GLuint mask)
+def glStencilFunc(func, ref, mask):
+    _lib.glStencilFunc(func, ref, mask)
+
+
+_lib.glStencilFuncSeparate.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.c_uint,
+# void = glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask)
+def glStencilFuncSeparate(face, func, ref, mask):
+    _lib.glStencilFuncSeparate(face, func, ref, mask)
+
+
+_lib.glStencilMask.argtypes = ctypes.c_uint,
+# void = glStencilMask(GLuint mask)
+def glStencilMask(mask):
+    _lib.glStencilMask(mask)
+
+
+_lib.glStencilMaskSeparate.argtypes = ctypes.c_uint, ctypes.c_uint,
+# void = glStencilMaskSeparate(GLenum face, GLuint mask)
+def glStencilMaskSeparate(face, mask):
+    _lib.glStencilMaskSeparate(face, mask)
+
+
+_lib.glStencilOp.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,
+# void = glStencilOp(GLenum fail, GLenum zfail, GLenum zpass)
+def glStencilOp(fail, zfail, zpass):
+    _lib.glStencilOp(fail, zfail, zpass)
+
+
+_lib.glStencilOpSeparate.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,
+# void = glStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass)
+def glStencilOpSeparate(face, fail, zfail, zpass):
+    _lib.glStencilOpSeparate(face, fail, zfail, zpass)
+
+
+_lib.glTexImage2D.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p,
+# void = glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLvoid* pixels)
+def glTexImage2D(target, level, internalformat, format, type, pixels):
+    border = 0
+    if isinstance(pixels, (tuple, list)):
+        height, width = pixels
+        pixels = ctypes.c_void_p(0)
+        pixels = None
+    else:
+        if not pixels.flags['C_CONTIGUOUS']:
+            pixels = pixels.copy('C')
+        pixels_ = pixels
+        pixels = pixels_.ctypes.data
+        height, width = pixels_.shape[:2]
+    res = _lib.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
+
+
+_lib.glTexParameterf.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_float,
+def glTexParameterf(target, pname, param):
+    _lib.glTexParameterf(target, pname, param)
+_lib.glTexParameteri.argtypes = ctypes.c_uint, ctypes.c_uint, ctypes.c_int,
+def glTexParameteri(target, pname, param):
+    _lib.glTexParameteri(target, pname, param)
+
+
+_lib.glTexSubImage2D.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p,
+# void = glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
+def glTexSubImage2D(target, level, xoffset, yoffset, format, type, pixels):
+    if not pixels.flags['C_CONTIGUOUS']:
+        pixels = pixels.copy('C')
+    pixels_ = pixels
+    pixels = pixels_.ctypes.data
+    height, width = pixels_.shape[:2]
+    res = _lib.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
+
+
+_lib.glUniform1f.argtypes = ctypes.c_int, ctypes.c_float,
+def glUniform1f(location, v1):
+    _lib.glUniform1f(location, v1)
+_lib.glUniform2f.argtypes = ctypes.c_int, ctypes.c_float, ctypes.c_float,
+def glUniform2f(location, v1, v2):
+    _lib.glUniform2f(location, v1, v2)
+_lib.glUniform3f.argtypes = ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+def glUniform3f(location, v1, v2, v3):
+    _lib.glUniform3f(location, v1, v2, v3)
+_lib.glUniform4f.argtypes = ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+def glUniform4f(location, v1, v2, v3, v4):
+    _lib.glUniform4f(location, v1, v2, v3, v4)
+_lib.glUniform1i.argtypes = ctypes.c_int, ctypes.c_int,
+def glUniform1i(location, v1):
+    _lib.glUniform1i(location, v1)
+_lib.glUniform2i.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_int,
+def glUniform2i(location, v1, v2):
+    _lib.glUniform2i(location, v1, v2)
+_lib.glUniform3i.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
+def glUniform3i(location, v1, v2, v3):
+    _lib.glUniform3i(location, v1, v2, v3)
+_lib.glUniform4i.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
+def glUniform4i(location, v1, v2, v3, v4):
+    _lib.glUniform4i(location, v1, v2, v3, v4)
+_lib.glUniform1fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),
+def glUniform1fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    _lib.glUniform1fv(location, count, values)
+_lib.glUniform2fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),
+def glUniform2fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    _lib.glUniform2fv(location, count, values)
+_lib.glUniform3fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),
+def glUniform3fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    _lib.glUniform3fv(location, count, values)
+_lib.glUniform4fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),
+def glUniform4fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    _lib.glUniform4fv(location, count, values)
+_lib.glUniform1iv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),
+def glUniform1iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    _lib.glUniform1iv(location, count, values)
+_lib.glUniform2iv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),
+def glUniform2iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    _lib.glUniform2iv(location, count, values)
+_lib.glUniform3iv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),
+def glUniform3iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    _lib.glUniform3iv(location, count, values)
+_lib.glUniform4iv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),
+def glUniform4iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    _lib.glUniform4iv(location, count, values)
+
+
+_lib.glUniformMatrix2fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_bool, ctypes.POINTER(ctypes.c_float),
+def glUniformMatrix2fv(location, count, transpose, values):
+    if not values.flags["C_CONTIGUOUS"]:
+        values = values.copy()
+    assert values.dtype.name == "float32"
+    values_ = values
+    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
+    _lib.glUniformMatrix2fv(location, count, transpose, values)
+_lib.glUniformMatrix3fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_bool, ctypes.POINTER(ctypes.c_float),
+def glUniformMatrix3fv(location, count, transpose, values):
+    if not values.flags["C_CONTIGUOUS"]:
+        values = values.copy()
+    assert values.dtype.name == "float32"
+    values_ = values
+    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
+    _lib.glUniformMatrix3fv(location, count, transpose, values)
+_lib.glUniformMatrix4fv.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_bool, ctypes.POINTER(ctypes.c_float),
+def glUniformMatrix4fv(location, count, transpose, values):
+    if not values.flags["C_CONTIGUOUS"]:
+        values = values.copy()
+    assert values.dtype.name == "float32"
+    values_ = values
+    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
+    _lib.glUniformMatrix4fv(location, count, transpose, values)
+
+
+_lib.glUseProgram.argtypes = ctypes.c_uint,
+# void = glUseProgram(GLuint program)
+def glUseProgram(program):
+    _lib.glUseProgram(program)
+
+
+_lib.glValidateProgram.argtypes = ctypes.c_uint,
+# void = glValidateProgram(GLuint program)
+def glValidateProgram(program):
+    _lib.glValidateProgram(program)
+
+
+_lib.glVertexAttrib1f.argtypes = ctypes.c_uint, ctypes.c_float,
+def glVertexAttrib1f(index, v1):
+    _lib.glVertexAttrib1f(index, v1)
+_lib.glVertexAttrib2f.argtypes = ctypes.c_uint, ctypes.c_float, ctypes.c_float,
+def glVertexAttrib2f(index, v1, v2):
+    _lib.glVertexAttrib2f(index, v1, v2)
+_lib.glVertexAttrib3f.argtypes = ctypes.c_uint, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+def glVertexAttrib3f(index, v1, v2, v3):
+    _lib.glVertexAttrib3f(index, v1, v2, v3)
+_lib.glVertexAttrib4f.argtypes = ctypes.c_uint, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,
+def glVertexAttrib4f(index, v1, v2, v3, v4):
+    _lib.glVertexAttrib4f(index, v1, v2, v3, v4)
+
+
+_lib.glVertexAttribPointer.argtypes = ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_bool, ctypes.c_int, ctypes.c_void_p,
+# void = glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLvoid* ptr)
+def glVertexAttribPointer(indx, size, type, normalized, stride, offset):
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, ctypes.c_void_p):
+        pass
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    else:
+        if not offset.flags['C_CONTIGUOUS']:
+            offset = offset.copy('C')
+        offset_ = offset
+        offset = offset.ctypes.data
+        # We need to ensure that the data exists at draw time :(
+        # PyOpenGL does this too
+        key = '_vert_attr_'+str(indx)
+        setattr(glVertexAttribPointer, key, offset_)
+    ptr = offset
+    res = _lib.glVertexAttribPointer(indx, size, type, normalized, stride, ptr)
+
+
+_lib.glViewport.argtypes = ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,
+# void = glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
+def glViewport(x, y, width, height):
+    _lib.glViewport(x, y, width, height)
+
+
diff --git a/vispy/gloo/gl/_constants.py b/vispy/gloo/gl/_constants.py
index 6fc4522..25051d6 100644
--- a/vispy/gloo/gl/_constants.py
+++ b/vispy/gloo/gl/_constants.py
@@ -1,4 +1,4 @@
-""" 
+"""
 
 THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
 
@@ -6,310 +6,323 @@ Constants for OpenGL ES 2.0.
 
 """
 
-from __future__ import print_function, division, absolute_import
 
-from . import _GL_ENUM
+class Enum(int):
+    ''' Enum (integer) with a meaningfull repr. '''
+    def __new__(cls, name, value):
+        base = int.__new__(cls, value)
+        base.name = name
+        return base
+    def __repr__(self):
+        return self.name
+
+
+GL_ACTIVE_ATTRIBUTES = Enum('GL_ACTIVE_ATTRIBUTES', 35721)
+GL_ACTIVE_ATTRIBUTE_MAX_LENGTH = Enum('GL_ACTIVE_ATTRIBUTE_MAX_LENGTH', 35722)
+GL_ACTIVE_TEXTURE = Enum('GL_ACTIVE_TEXTURE', 34016)
+GL_ACTIVE_UNIFORMS = Enum('GL_ACTIVE_UNIFORMS', 35718)
+GL_ACTIVE_UNIFORM_MAX_LENGTH = Enum('GL_ACTIVE_UNIFORM_MAX_LENGTH', 35719)
+GL_ALIASED_LINE_WIDTH_RANGE = Enum('GL_ALIASED_LINE_WIDTH_RANGE', 33902)
+GL_ALIASED_POINT_SIZE_RANGE = Enum('GL_ALIASED_POINT_SIZE_RANGE', 33901)
+GL_ALPHA = Enum('GL_ALPHA', 6406)
+GL_ALPHA_BITS = Enum('GL_ALPHA_BITS', 3413)
+GL_ALWAYS = Enum('GL_ALWAYS', 519)
+GL_ARRAY_BUFFER = Enum('GL_ARRAY_BUFFER', 34962)
+GL_ARRAY_BUFFER_BINDING = Enum('GL_ARRAY_BUFFER_BINDING', 34964)
+GL_ATTACHED_SHADERS = Enum('GL_ATTACHED_SHADERS', 35717)
+GL_BACK = Enum('GL_BACK', 1029)
+GL_BLEND = Enum('GL_BLEND', 3042)
+GL_BLEND_COLOR = Enum('GL_BLEND_COLOR', 32773)
+GL_BLEND_DST_ALPHA = Enum('GL_BLEND_DST_ALPHA', 32970)
+GL_BLEND_DST_RGB = Enum('GL_BLEND_DST_RGB', 32968)
+GL_BLEND_EQUATION = Enum('GL_BLEND_EQUATION', 32777)
+GL_BLEND_EQUATION_ALPHA = Enum('GL_BLEND_EQUATION_ALPHA', 34877)
+GL_BLEND_EQUATION_RGB = Enum('GL_BLEND_EQUATION_RGB', 32777)
+GL_BLEND_SRC_ALPHA = Enum('GL_BLEND_SRC_ALPHA', 32971)
+GL_BLEND_SRC_RGB = Enum('GL_BLEND_SRC_RGB', 32969)
+GL_BLUE_BITS = Enum('GL_BLUE_BITS', 3412)
+GL_BOOL = Enum('GL_BOOL', 35670)
+GL_BOOL_VEC2 = Enum('GL_BOOL_VEC2', 35671)
+GL_BOOL_VEC3 = Enum('GL_BOOL_VEC3', 35672)
+GL_BOOL_VEC4 = Enum('GL_BOOL_VEC4', 35673)
+GL_BUFFER_SIZE = Enum('GL_BUFFER_SIZE', 34660)
+GL_BUFFER_USAGE = Enum('GL_BUFFER_USAGE', 34661)
+GL_BYTE = Enum('GL_BYTE', 5120)
+GL_CCW = Enum('GL_CCW', 2305)
+GL_CLAMP_TO_EDGE = Enum('GL_CLAMP_TO_EDGE', 33071)
+GL_COLOR_ATTACHMENT0 = Enum('GL_COLOR_ATTACHMENT0', 36064)
+GL_COLOR_BUFFER_BIT = Enum('GL_COLOR_BUFFER_BIT', 16384)
+GL_COLOR_CLEAR_VALUE = Enum('GL_COLOR_CLEAR_VALUE', 3106)
+GL_COLOR_WRITEMASK = Enum('GL_COLOR_WRITEMASK', 3107)
+GL_COMPILE_STATUS = Enum('GL_COMPILE_STATUS', 35713)
+GL_COMPRESSED_TEXTURE_FORMATS = Enum('GL_COMPRESSED_TEXTURE_FORMATS', 34467)
+GL_CONSTANT_ALPHA = Enum('GL_CONSTANT_ALPHA', 32771)
+GL_CONSTANT_COLOR = Enum('GL_CONSTANT_COLOR', 32769)
+GL_CULL_FACE = Enum('GL_CULL_FACE', 2884)
+GL_CULL_FACE_MODE = Enum('GL_CULL_FACE_MODE', 2885)
+GL_CURRENT_PROGRAM = Enum('GL_CURRENT_PROGRAM', 35725)
+GL_CURRENT_VERTEX_ATTRIB = Enum('GL_CURRENT_VERTEX_ATTRIB', 34342)
+GL_CW = Enum('GL_CW', 2304)
+GL_DECR = Enum('GL_DECR', 7683)
+GL_DECR_WRAP = Enum('GL_DECR_WRAP', 34056)
+GL_DELETE_STATUS = Enum('GL_DELETE_STATUS', 35712)
+GL_DEPTH_ATTACHMENT = Enum('GL_DEPTH_ATTACHMENT', 36096)
+GL_DEPTH_BITS = Enum('GL_DEPTH_BITS', 3414)
+GL_DEPTH_BUFFER_BIT = Enum('GL_DEPTH_BUFFER_BIT', 256)
+GL_DEPTH_CLEAR_VALUE = Enum('GL_DEPTH_CLEAR_VALUE', 2931)
+GL_DEPTH_COMPONENT = Enum('GL_DEPTH_COMPONENT', 6402)
+GL_DEPTH_COMPONENT16 = Enum('GL_DEPTH_COMPONENT16', 33189)
+GL_DEPTH_FUNC = Enum('GL_DEPTH_FUNC', 2932)
+GL_DEPTH_RANGE = Enum('GL_DEPTH_RANGE', 2928)
+GL_DEPTH_TEST = Enum('GL_DEPTH_TEST', 2929)
+GL_DEPTH_WRITEMASK = Enum('GL_DEPTH_WRITEMASK', 2930)
+GL_DITHER = Enum('GL_DITHER', 3024)
+GL_DONT_CARE = Enum('GL_DONT_CARE', 4352)
+GL_DST_ALPHA = Enum('GL_DST_ALPHA', 772)
+GL_DST_COLOR = Enum('GL_DST_COLOR', 774)
+GL_DYNAMIC_DRAW = Enum('GL_DYNAMIC_DRAW', 35048)
+GL_ELEMENT_ARRAY_BUFFER = Enum('GL_ELEMENT_ARRAY_BUFFER', 34963)
+GL_ELEMENT_ARRAY_BUFFER_BINDING = Enum('GL_ELEMENT_ARRAY_BUFFER_BINDING', 34965)
+GL_EQUAL = Enum('GL_EQUAL', 514)
+GL_ES_VERSION_2_0 = Enum('GL_ES_VERSION_2_0', 1)
+GL_EXTENSIONS = Enum('GL_EXTENSIONS', 7939)
+GL_FALSE = Enum('GL_FALSE', 0)
+GL_FASTEST = Enum('GL_FASTEST', 4353)
+GL_FIXED = Enum('GL_FIXED', 5132)
+GL_FLOAT = Enum('GL_FLOAT', 5126)
+GL_FLOAT_MAT2 = Enum('GL_FLOAT_MAT2', 35674)
+GL_FLOAT_MAT3 = Enum('GL_FLOAT_MAT3', 35675)
+GL_FLOAT_MAT4 = Enum('GL_FLOAT_MAT4', 35676)
+GL_FLOAT_VEC2 = Enum('GL_FLOAT_VEC2', 35664)
+GL_FLOAT_VEC3 = Enum('GL_FLOAT_VEC3', 35665)
+GL_FLOAT_VEC4 = Enum('GL_FLOAT_VEC4', 35666)
+GL_FRAGMENT_SHADER = Enum('GL_FRAGMENT_SHADER', 35632)
+GL_FRAMEBUFFER = Enum('GL_FRAMEBUFFER', 36160)
+GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = Enum('GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME', 36049)
+GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = Enum('GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE', 36048)
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = Enum('GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE', 36051)
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = Enum('GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL', 36050)
+GL_FRAMEBUFFER_BINDING = Enum('GL_FRAMEBUFFER_BINDING', 36006)
+GL_FRAMEBUFFER_COMPLETE = Enum('GL_FRAMEBUFFER_COMPLETE', 36053)
+GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = Enum('GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT', 36054)
+GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = Enum('GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS', 36057)
+GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = Enum('GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT', 36055)
+GL_FRAMEBUFFER_UNSUPPORTED = Enum('GL_FRAMEBUFFER_UNSUPPORTED', 36061)
+GL_FRONT = Enum('GL_FRONT', 1028)
+GL_FRONT_AND_BACK = Enum('GL_FRONT_AND_BACK', 1032)
+GL_FRONT_FACE = Enum('GL_FRONT_FACE', 2886)
+GL_FUNC_ADD = Enum('GL_FUNC_ADD', 32774)
+GL_FUNC_REVERSE_SUBTRACT = Enum('GL_FUNC_REVERSE_SUBTRACT', 32779)
+GL_FUNC_SUBTRACT = Enum('GL_FUNC_SUBTRACT', 32778)
+GL_GENERATE_MIPMAP_HINT = Enum('GL_GENERATE_MIPMAP_HINT', 33170)
+GL_GEQUAL = Enum('GL_GEQUAL', 518)
+GL_GREATER = Enum('GL_GREATER', 516)
+GL_GREEN_BITS = Enum('GL_GREEN_BITS', 3411)
+GL_HIGH_FLOAT = Enum('GL_HIGH_FLOAT', 36338)
+GL_HIGH_INT = Enum('GL_HIGH_INT', 36341)
+GL_IMPLEMENTATION_COLOR_READ_FORMAT = Enum('GL_IMPLEMENTATION_COLOR_READ_FORMAT', 35739)
+GL_IMPLEMENTATION_COLOR_READ_TYPE = Enum('GL_IMPLEMENTATION_COLOR_READ_TYPE', 35738)
+GL_INCR = Enum('GL_INCR', 7682)
+GL_INCR_WRAP = Enum('GL_INCR_WRAP', 34055)
+GL_INFO_LOG_LENGTH = Enum('GL_INFO_LOG_LENGTH', 35716)
+GL_INT = Enum('GL_INT', 5124)
+GL_INT_VEC2 = Enum('GL_INT_VEC2', 35667)
+GL_INT_VEC3 = Enum('GL_INT_VEC3', 35668)
+GL_INT_VEC4 = Enum('GL_INT_VEC4', 35669)
+GL_INVALID_ENUM = Enum('GL_INVALID_ENUM', 1280)
+GL_INVALID_FRAMEBUFFER_OPERATION = Enum('GL_INVALID_FRAMEBUFFER_OPERATION', 1286)
+GL_INVALID_OPERATION = Enum('GL_INVALID_OPERATION', 1282)
+GL_INVALID_VALUE = Enum('GL_INVALID_VALUE', 1281)
+GL_INVERT = Enum('GL_INVERT', 5386)
+GL_KEEP = Enum('GL_KEEP', 7680)
+GL_LEQUAL = Enum('GL_LEQUAL', 515)
+GL_LESS = Enum('GL_LESS', 513)
+GL_LINEAR = Enum('GL_LINEAR', 9729)
+GL_LINEAR_MIPMAP_LINEAR = Enum('GL_LINEAR_MIPMAP_LINEAR', 9987)
+GL_LINEAR_MIPMAP_NEAREST = Enum('GL_LINEAR_MIPMAP_NEAREST', 9985)
+GL_LINES = Enum('GL_LINES', 1)
+GL_LINE_LOOP = Enum('GL_LINE_LOOP', 2)
+GL_LINE_STRIP = Enum('GL_LINE_STRIP', 3)
+GL_LINE_WIDTH = Enum('GL_LINE_WIDTH', 2849)
+GL_LINK_STATUS = Enum('GL_LINK_STATUS', 35714)
+GL_LOW_FLOAT = Enum('GL_LOW_FLOAT', 36336)
+GL_LOW_INT = Enum('GL_LOW_INT', 36339)
+GL_LUMINANCE = Enum('GL_LUMINANCE', 6409)
+GL_LUMINANCE_ALPHA = Enum('GL_LUMINANCE_ALPHA', 6410)
+GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = Enum('GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS', 35661)
+GL_MAX_CUBE_MAP_TEXTURE_SIZE = Enum('GL_MAX_CUBE_MAP_TEXTURE_SIZE', 34076)
+GL_MAX_FRAGMENT_UNIFORM_VECTORS = Enum('GL_MAX_FRAGMENT_UNIFORM_VECTORS', 36349)
+GL_MAX_RENDERBUFFER_SIZE = Enum('GL_MAX_RENDERBUFFER_SIZE', 34024)
+GL_MAX_TEXTURE_IMAGE_UNITS = Enum('GL_MAX_TEXTURE_IMAGE_UNITS', 34930)
+GL_MAX_TEXTURE_SIZE = Enum('GL_MAX_TEXTURE_SIZE', 3379)
+GL_MAX_VARYING_VECTORS = Enum('GL_MAX_VARYING_VECTORS', 36348)
+GL_MAX_VERTEX_ATTRIBS = Enum('GL_MAX_VERTEX_ATTRIBS', 34921)
+GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = Enum('GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS', 35660)
+GL_MAX_VERTEX_UNIFORM_VECTORS = Enum('GL_MAX_VERTEX_UNIFORM_VECTORS', 36347)
+GL_MAX_VIEWPORT_DIMS = Enum('GL_MAX_VIEWPORT_DIMS', 3386)
+GL_MEDIUM_FLOAT = Enum('GL_MEDIUM_FLOAT', 36337)
+GL_MEDIUM_INT = Enum('GL_MEDIUM_INT', 36340)
+GL_MIRRORED_REPEAT = Enum('GL_MIRRORED_REPEAT', 33648)
+GL_NEAREST = Enum('GL_NEAREST', 9728)
+GL_NEAREST_MIPMAP_LINEAR = Enum('GL_NEAREST_MIPMAP_LINEAR', 9986)
+GL_NEAREST_MIPMAP_NEAREST = Enum('GL_NEAREST_MIPMAP_NEAREST', 9984)
+GL_NEVER = Enum('GL_NEVER', 512)
+GL_NICEST = Enum('GL_NICEST', 4354)
+GL_NONE = Enum('GL_NONE', 0)
+GL_NOTEQUAL = Enum('GL_NOTEQUAL', 517)
+GL_NO_ERROR = Enum('GL_NO_ERROR', 0)
+GL_NUM_COMPRESSED_TEXTURE_FORMATS = Enum('GL_NUM_COMPRESSED_TEXTURE_FORMATS', 34466)
+GL_NUM_SHADER_BINARY_FORMATS = Enum('GL_NUM_SHADER_BINARY_FORMATS', 36345)
+GL_ONE = Enum('GL_ONE', 1)
+GL_ONE_MINUS_CONSTANT_ALPHA = Enum('GL_ONE_MINUS_CONSTANT_ALPHA', 32772)
+GL_ONE_MINUS_CONSTANT_COLOR = Enum('GL_ONE_MINUS_CONSTANT_COLOR', 32770)
+GL_ONE_MINUS_DST_ALPHA = Enum('GL_ONE_MINUS_DST_ALPHA', 773)
+GL_ONE_MINUS_DST_COLOR = Enum('GL_ONE_MINUS_DST_COLOR', 775)
+GL_ONE_MINUS_SRC_ALPHA = Enum('GL_ONE_MINUS_SRC_ALPHA', 771)
+GL_ONE_MINUS_SRC_COLOR = Enum('GL_ONE_MINUS_SRC_COLOR', 769)
+GL_OUT_OF_MEMORY = Enum('GL_OUT_OF_MEMORY', 1285)
+GL_PACK_ALIGNMENT = Enum('GL_PACK_ALIGNMENT', 3333)
+GL_POINTS = Enum('GL_POINTS', 0)
+GL_POLYGON_OFFSET_FACTOR = Enum('GL_POLYGON_OFFSET_FACTOR', 32824)
+GL_POLYGON_OFFSET_FILL = Enum('GL_POLYGON_OFFSET_FILL', 32823)
+GL_POLYGON_OFFSET_UNITS = Enum('GL_POLYGON_OFFSET_UNITS', 10752)
+GL_RED_BITS = Enum('GL_RED_BITS', 3410)
+GL_RENDERBUFFER = Enum('GL_RENDERBUFFER', 36161)
+GL_RENDERBUFFER_ALPHA_SIZE = Enum('GL_RENDERBUFFER_ALPHA_SIZE', 36179)
+GL_RENDERBUFFER_BINDING = Enum('GL_RENDERBUFFER_BINDING', 36007)
+GL_RENDERBUFFER_BLUE_SIZE = Enum('GL_RENDERBUFFER_BLUE_SIZE', 36178)
+GL_RENDERBUFFER_DEPTH_SIZE = Enum('GL_RENDERBUFFER_DEPTH_SIZE', 36180)
+GL_RENDERBUFFER_GREEN_SIZE = Enum('GL_RENDERBUFFER_GREEN_SIZE', 36177)
+GL_RENDERBUFFER_HEIGHT = Enum('GL_RENDERBUFFER_HEIGHT', 36163)
+GL_RENDERBUFFER_INTERNAL_FORMAT = Enum('GL_RENDERBUFFER_INTERNAL_FORMAT', 36164)
+GL_RENDERBUFFER_RED_SIZE = Enum('GL_RENDERBUFFER_RED_SIZE', 36176)
+GL_RENDERBUFFER_STENCIL_SIZE = Enum('GL_RENDERBUFFER_STENCIL_SIZE', 36181)
+GL_RENDERBUFFER_WIDTH = Enum('GL_RENDERBUFFER_WIDTH', 36162)
+GL_RENDERER = Enum('GL_RENDERER', 7937)
+GL_REPEAT = Enum('GL_REPEAT', 10497)
+GL_REPLACE = Enum('GL_REPLACE', 7681)
+GL_RGB = Enum('GL_RGB', 6407)
+GL_RGB565 = Enum('GL_RGB565', 36194)
+GL_RGB5_A1 = Enum('GL_RGB5_A1', 32855)
+GL_RGBA = Enum('GL_RGBA', 6408)
+GL_RGBA4 = Enum('GL_RGBA4', 32854)
+GL_SAMPLER_2D = Enum('GL_SAMPLER_2D', 35678)
+GL_SAMPLER_CUBE = Enum('GL_SAMPLER_CUBE', 35680)
+GL_SAMPLES = Enum('GL_SAMPLES', 32937)
+GL_SAMPLE_ALPHA_TO_COVERAGE = Enum('GL_SAMPLE_ALPHA_TO_COVERAGE', 32926)
+GL_SAMPLE_BUFFERS = Enum('GL_SAMPLE_BUFFERS', 32936)
+GL_SAMPLE_COVERAGE = Enum('GL_SAMPLE_COVERAGE', 32928)
+GL_SAMPLE_COVERAGE_INVERT = Enum('GL_SAMPLE_COVERAGE_INVERT', 32939)
+GL_SAMPLE_COVERAGE_VALUE = Enum('GL_SAMPLE_COVERAGE_VALUE', 32938)
+GL_SCISSOR_BOX = Enum('GL_SCISSOR_BOX', 3088)
+GL_SCISSOR_TEST = Enum('GL_SCISSOR_TEST', 3089)
+GL_SHADER_BINARY_FORMATS = Enum('GL_SHADER_BINARY_FORMATS', 36344)
+GL_SHADER_COMPILER = Enum('GL_SHADER_COMPILER', 36346)
+GL_SHADER_SOURCE_LENGTH = Enum('GL_SHADER_SOURCE_LENGTH', 35720)
+GL_SHADER_TYPE = Enum('GL_SHADER_TYPE', 35663)
+GL_SHADING_LANGUAGE_VERSION = Enum('GL_SHADING_LANGUAGE_VERSION', 35724)
+GL_SHORT = Enum('GL_SHORT', 5122)
+GL_SRC_ALPHA = Enum('GL_SRC_ALPHA', 770)
+GL_SRC_ALPHA_SATURATE = Enum('GL_SRC_ALPHA_SATURATE', 776)
+GL_SRC_COLOR = Enum('GL_SRC_COLOR', 768)
+GL_STATIC_DRAW = Enum('GL_STATIC_DRAW', 35044)
+GL_STENCIL_ATTACHMENT = Enum('GL_STENCIL_ATTACHMENT', 36128)
+GL_STENCIL_BACK_FAIL = Enum('GL_STENCIL_BACK_FAIL', 34817)
+GL_STENCIL_BACK_FUNC = Enum('GL_STENCIL_BACK_FUNC', 34816)
+GL_STENCIL_BACK_PASS_DEPTH_FAIL = Enum('GL_STENCIL_BACK_PASS_DEPTH_FAIL', 34818)
+GL_STENCIL_BACK_PASS_DEPTH_PASS = Enum('GL_STENCIL_BACK_PASS_DEPTH_PASS', 34819)
+GL_STENCIL_BACK_REF = Enum('GL_STENCIL_BACK_REF', 36003)
+GL_STENCIL_BACK_VALUE_MASK = Enum('GL_STENCIL_BACK_VALUE_MASK', 36004)
+GL_STENCIL_BACK_WRITEMASK = Enum('GL_STENCIL_BACK_WRITEMASK', 36005)
+GL_STENCIL_BITS = Enum('GL_STENCIL_BITS', 3415)
+GL_STENCIL_BUFFER_BIT = Enum('GL_STENCIL_BUFFER_BIT', 1024)
+GL_STENCIL_CLEAR_VALUE = Enum('GL_STENCIL_CLEAR_VALUE', 2961)
+GL_STENCIL_FAIL = Enum('GL_STENCIL_FAIL', 2964)
+GL_STENCIL_FUNC = Enum('GL_STENCIL_FUNC', 2962)
+GL_STENCIL_INDEX8 = Enum('GL_STENCIL_INDEX8', 36168)
+GL_STENCIL_PASS_DEPTH_FAIL = Enum('GL_STENCIL_PASS_DEPTH_FAIL', 2965)
+GL_STENCIL_PASS_DEPTH_PASS = Enum('GL_STENCIL_PASS_DEPTH_PASS', 2966)
+GL_STENCIL_REF = Enum('GL_STENCIL_REF', 2967)
+GL_STENCIL_TEST = Enum('GL_STENCIL_TEST', 2960)
+GL_STENCIL_VALUE_MASK = Enum('GL_STENCIL_VALUE_MASK', 2963)
+GL_STENCIL_WRITEMASK = Enum('GL_STENCIL_WRITEMASK', 2968)
+GL_STREAM_DRAW = Enum('GL_STREAM_DRAW', 35040)
+GL_SUBPIXEL_BITS = Enum('GL_SUBPIXEL_BITS', 3408)
+GL_TEXTURE = Enum('GL_TEXTURE', 5890)
+GL_TEXTURE0 = Enum('GL_TEXTURE0', 33984)
+GL_TEXTURE1 = Enum('GL_TEXTURE1', 33985)
+GL_TEXTURE10 = Enum('GL_TEXTURE10', 33994)
+GL_TEXTURE11 = Enum('GL_TEXTURE11', 33995)
+GL_TEXTURE12 = Enum('GL_TEXTURE12', 33996)
+GL_TEXTURE13 = Enum('GL_TEXTURE13', 33997)
+GL_TEXTURE14 = Enum('GL_TEXTURE14', 33998)
+GL_TEXTURE15 = Enum('GL_TEXTURE15', 33999)
+GL_TEXTURE16 = Enum('GL_TEXTURE16', 34000)
+GL_TEXTURE17 = Enum('GL_TEXTURE17', 34001)
+GL_TEXTURE18 = Enum('GL_TEXTURE18', 34002)
+GL_TEXTURE19 = Enum('GL_TEXTURE19', 34003)
+GL_TEXTURE2 = Enum('GL_TEXTURE2', 33986)
+GL_TEXTURE20 = Enum('GL_TEXTURE20', 34004)
+GL_TEXTURE21 = Enum('GL_TEXTURE21', 34005)
+GL_TEXTURE22 = Enum('GL_TEXTURE22', 34006)
+GL_TEXTURE23 = Enum('GL_TEXTURE23', 34007)
+GL_TEXTURE24 = Enum('GL_TEXTURE24', 34008)
+GL_TEXTURE25 = Enum('GL_TEXTURE25', 34009)
+GL_TEXTURE26 = Enum('GL_TEXTURE26', 34010)
+GL_TEXTURE27 = Enum('GL_TEXTURE27', 34011)
+GL_TEXTURE28 = Enum('GL_TEXTURE28', 34012)
+GL_TEXTURE29 = Enum('GL_TEXTURE29', 34013)
+GL_TEXTURE3 = Enum('GL_TEXTURE3', 33987)
+GL_TEXTURE30 = Enum('GL_TEXTURE30', 34014)
+GL_TEXTURE31 = Enum('GL_TEXTURE31', 34015)
+GL_TEXTURE4 = Enum('GL_TEXTURE4', 33988)
+GL_TEXTURE5 = Enum('GL_TEXTURE5', 33989)
+GL_TEXTURE6 = Enum('GL_TEXTURE6', 33990)
+GL_TEXTURE7 = Enum('GL_TEXTURE7', 33991)
+GL_TEXTURE8 = Enum('GL_TEXTURE8', 33992)
+GL_TEXTURE9 = Enum('GL_TEXTURE9', 33993)
+GL_TEXTURE_2D = Enum('GL_TEXTURE_2D', 3553)
+GL_TEXTURE_BINDING_2D = Enum('GL_TEXTURE_BINDING_2D', 32873)
+GL_TEXTURE_BINDING_CUBE_MAP = Enum('GL_TEXTURE_BINDING_CUBE_MAP', 34068)
+GL_TEXTURE_CUBE_MAP = Enum('GL_TEXTURE_CUBE_MAP', 34067)
+GL_TEXTURE_CUBE_MAP_NEGATIVE_X = Enum('GL_TEXTURE_CUBE_MAP_NEGATIVE_X', 34070)
+GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = Enum('GL_TEXTURE_CUBE_MAP_NEGATIVE_Y', 34072)
+GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = Enum('GL_TEXTURE_CUBE_MAP_NEGATIVE_Z', 34074)
+GL_TEXTURE_CUBE_MAP_POSITIVE_X = Enum('GL_TEXTURE_CUBE_MAP_POSITIVE_X', 34069)
+GL_TEXTURE_CUBE_MAP_POSITIVE_Y = Enum('GL_TEXTURE_CUBE_MAP_POSITIVE_Y', 34071)
+GL_TEXTURE_CUBE_MAP_POSITIVE_Z = Enum('GL_TEXTURE_CUBE_MAP_POSITIVE_Z', 34073)
+GL_TEXTURE_MAG_FILTER = Enum('GL_TEXTURE_MAG_FILTER', 10240)
+GL_TEXTURE_MIN_FILTER = Enum('GL_TEXTURE_MIN_FILTER', 10241)
+GL_TEXTURE_WRAP_S = Enum('GL_TEXTURE_WRAP_S', 10242)
+GL_TEXTURE_WRAP_T = Enum('GL_TEXTURE_WRAP_T', 10243)
+GL_TRIANGLES = Enum('GL_TRIANGLES', 4)
+GL_TRIANGLE_FAN = Enum('GL_TRIANGLE_FAN', 6)
+GL_TRIANGLE_STRIP = Enum('GL_TRIANGLE_STRIP', 5)
+GL_TRUE = Enum('GL_TRUE', 1)
+GL_UNPACK_ALIGNMENT = Enum('GL_UNPACK_ALIGNMENT', 3317)
+GL_UNSIGNED_BYTE = Enum('GL_UNSIGNED_BYTE', 5121)
+GL_UNSIGNED_INT = Enum('GL_UNSIGNED_INT', 5125)
+GL_UNSIGNED_SHORT = Enum('GL_UNSIGNED_SHORT', 5123)
+GL_UNSIGNED_SHORT_4_4_4_4 = Enum('GL_UNSIGNED_SHORT_4_4_4_4', 32819)
+GL_UNSIGNED_SHORT_5_5_5_1 = Enum('GL_UNSIGNED_SHORT_5_5_5_1', 32820)
+GL_UNSIGNED_SHORT_5_6_5 = Enum('GL_UNSIGNED_SHORT_5_6_5', 33635)
+GL_VALIDATE_STATUS = Enum('GL_VALIDATE_STATUS', 35715)
+GL_VENDOR = Enum('GL_VENDOR', 7936)
+GL_VERSION = Enum('GL_VERSION', 7938)
+GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = Enum('GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING', 34975)
+GL_VERTEX_ATTRIB_ARRAY_ENABLED = Enum('GL_VERTEX_ATTRIB_ARRAY_ENABLED', 34338)
+GL_VERTEX_ATTRIB_ARRAY_NORMALIZED = Enum('GL_VERTEX_ATTRIB_ARRAY_NORMALIZED', 34922)
+GL_VERTEX_ATTRIB_ARRAY_POINTER = Enum('GL_VERTEX_ATTRIB_ARRAY_POINTER', 34373)
+GL_VERTEX_ATTRIB_ARRAY_SIZE = Enum('GL_VERTEX_ATTRIB_ARRAY_SIZE', 34339)
+GL_VERTEX_ATTRIB_ARRAY_STRIDE = Enum('GL_VERTEX_ATTRIB_ARRAY_STRIDE', 34340)
+GL_VERTEX_ATTRIB_ARRAY_TYPE = Enum('GL_VERTEX_ATTRIB_ARRAY_TYPE', 34341)
+GL_VERTEX_SHADER = Enum('GL_VERTEX_SHADER', 35633)
+GL_VIEWPORT = Enum('GL_VIEWPORT', 2978)
+GL_ZERO = Enum('GL_ZERO', 0)
 
 
-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)
+ENUM_MAP = {}
+for ob in list(globals().values()):
+    if repr(ob).startswith('GL_'):
+        ENUM_MAP[int(ob)] = ob
+del ob
diff --git a/vispy/gloo/gl/_constants_ext.py b/vispy/gloo/gl/_constants_ext.py
deleted file mode 100644
index d350945..0000000
--- a/vispy/gloo/gl/_constants_ext.py
+++ /dev/null
@@ -1,96 +0,0 @@
-""" 
-
-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
index 888a1b5..e531c66 100644
--- a/vispy/gloo/gl/_desktop.py
+++ b/vispy/gloo/gl/_desktop.py
@@ -1,158 +1,1365 @@
-""" 
+"""
 
 THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
 
-OpenGL ES 2.0 API based on desktop OpenGL (via pyOpenGL).
+Subset of desktop GL API compatible with GL ES 2.0
 
 """
 
-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",
-    ]
+import ctypes
+from .desktop import _lib, _get_gl_func
+
+
+# void = glActiveTexture(GLenum texture)
+def glActiveTexture(texture):
+    try:
+        nativefunc = glActiveTexture._native
+    except AttributeError:
+        nativefunc = glActiveTexture._native = _get_gl_func("glActiveTexture", None, (ctypes.c_uint,))
+    nativefunc(texture)
+
+
+# void = glAttachShader(GLuint program, GLuint shader)
+def glAttachShader(program, shader):
+    try:
+        nativefunc = glAttachShader._native
+    except AttributeError:
+        nativefunc = glAttachShader._native = _get_gl_func("glAttachShader", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(program, shader)
+
+
+# void = glBindAttribLocation(GLuint program, GLuint index, GLchar* name)
+def glBindAttribLocation(program, index, name):
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    try:
+        nativefunc = glBindAttribLocation._native
+    except AttributeError:
+        nativefunc = glBindAttribLocation._native = _get_gl_func("glBindAttribLocation", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_char_p,))
+    res = nativefunc(program, index, name)
+
+
+# void = glBindBuffer(GLenum target, GLuint buffer)
+def glBindBuffer(target, buffer):
+    try:
+        nativefunc = glBindBuffer._native
+    except AttributeError:
+        nativefunc = glBindBuffer._native = _get_gl_func("glBindBuffer", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(target, buffer)
+
+
+# void = glBindFramebuffer(GLenum target, GLuint framebuffer)
+def glBindFramebuffer(target, framebuffer):
+    try:
+        nativefunc = glBindFramebuffer._native
+    except AttributeError:
+        nativefunc = glBindFramebuffer._native = _get_gl_func("glBindFramebuffer", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(target, framebuffer)
+
+
+# void = glBindRenderbuffer(GLenum target, GLuint renderbuffer)
+def glBindRenderbuffer(target, renderbuffer):
+    try:
+        nativefunc = glBindRenderbuffer._native
+    except AttributeError:
+        nativefunc = glBindRenderbuffer._native = _get_gl_func("glBindRenderbuffer", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(target, renderbuffer)
+
+
+# void = glBindTexture(GLenum target, GLuint texture)
+def glBindTexture(target, texture):
+    try:
+        nativefunc = glBindTexture._native
+    except AttributeError:
+        nativefunc = glBindTexture._native = _get_gl_func("glBindTexture", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(target, texture)
+
+
+# void = glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
+def glBlendColor(red, green, blue, alpha):
+    try:
+        nativefunc = glBlendColor._native
+    except AttributeError:
+        nativefunc = glBlendColor._native = _get_gl_func("glBlendColor", None, (ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,))
+    nativefunc(red, green, blue, alpha)
+
+
+# void = glBlendEquation(GLenum mode)
+def glBlendEquation(mode):
+    try:
+        nativefunc = glBlendEquation._native
+    except AttributeError:
+        nativefunc = glBlendEquation._native = _get_gl_func("glBlendEquation", None, (ctypes.c_uint,))
+    nativefunc(mode)
+
+
+# void = glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha)
+def glBlendEquationSeparate(modeRGB, modeAlpha):
+    try:
+        nativefunc = glBlendEquationSeparate._native
+    except AttributeError:
+        nativefunc = glBlendEquationSeparate._native = _get_gl_func("glBlendEquationSeparate", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(modeRGB, modeAlpha)
+
+
+# void = glBlendFunc(GLenum sfactor, GLenum dfactor)
+def glBlendFunc(sfactor, dfactor):
+    try:
+        nativefunc = glBlendFunc._native
+    except AttributeError:
+        nativefunc = glBlendFunc._native = _get_gl_func("glBlendFunc", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(sfactor, dfactor)
+
+
+# void = glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha)
+def glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha):
+    try:
+        nativefunc = glBlendFuncSeparate._native
+    except AttributeError:
+        nativefunc = glBlendFuncSeparate._native = _get_gl_func("glBlendFuncSeparate", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(srcRGB, dstRGB, srcAlpha, dstAlpha)
+
+
+# void = glBufferData(GLenum target, GLsizeiptr size, GLvoid* data, GLenum usage)
+def glBufferData(target, data, usage):
+    """ Data can be numpy array or the size of data to allocate.
+    """
+    if isinstance(data, int):
+        size = data
+        data = ctypes.c_voidp(0)
+    else:
+        if not data.flags['C_CONTIGUOUS'] or not data.flags['ALIGNED']:
+            data = data.copy('C')
+        data_ = data
+        size = data_.nbytes
+        data = data_.ctypes.data
+    try:
+        nativefunc = glBufferData._native
+    except AttributeError:
+        nativefunc = glBufferData._native = _get_gl_func("glBufferData", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint,))
+    res = nativefunc(target, size, data, usage)
+
+
+# void = glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data)
+def glBufferSubData(target, offset, data):
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.nbytes
+    data = data_.ctypes.data
+    try:
+        nativefunc = glBufferSubData._native
+    except AttributeError:
+        nativefunc = glBufferSubData._native = _get_gl_func("glBufferSubData", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_void_p,))
+    res = nativefunc(target, offset, size, data)
+
+
+# GLenum = glCheckFramebufferStatus(GLenum target)
+def glCheckFramebufferStatus(target):
+    try:
+        nativefunc = glCheckFramebufferStatus._native
+    except AttributeError:
+        nativefunc = glCheckFramebufferStatus._native = _get_gl_func("glCheckFramebufferStatus", ctypes.c_uint, (ctypes.c_uint,))
+    return nativefunc(target)
+
+
+# void = glClear(GLbitfield mask)
+def glClear(mask):
+    try:
+        nativefunc = glClear._native
+    except AttributeError:
+        nativefunc = glClear._native = _get_gl_func("glClear", None, (ctypes.c_uint,))
+    nativefunc(mask)
+
+
+# void = glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
+def glClearColor(red, green, blue, alpha):
+    try:
+        nativefunc = glClearColor._native
+    except AttributeError:
+        nativefunc = glClearColor._native = _get_gl_func("glClearColor", None, (ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,))
+    nativefunc(red, green, blue, alpha)
+
+
+# void = glClearDepthf(GLclampf depth)
+def glClearDepth(depth):
+    try:
+        nativefunc = glClearDepth._native
+    except AttributeError:
+        nativefunc = glClearDepth._native = _get_gl_func("glClearDepth", None, (ctypes.c_double,))
+    nativefunc(depth)
+
+
+# void = glClearStencil(GLint s)
+def glClearStencil(s):
+    try:
+        nativefunc = glClearStencil._native
+    except AttributeError:
+        nativefunc = glClearStencil._native = _get_gl_func("glClearStencil", None, (ctypes.c_int,))
+    nativefunc(s)
+
+
+# void = glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)
+def glColorMask(red, green, blue, alpha):
+    try:
+        nativefunc = glColorMask._native
+    except AttributeError:
+        nativefunc = glColorMask._native = _get_gl_func("glColorMask", None, (ctypes.c_bool, ctypes.c_bool, ctypes.c_bool, ctypes.c_bool,))
+    nativefunc(red, green, blue, alpha)
+
+
+# void = glCompileShader(GLuint shader)
+def glCompileShader(shader):
+    try:
+        nativefunc = glCompileShader._native
+    except AttributeError:
+        nativefunc = glCompileShader._native = _get_gl_func("glCompileShader", None, (ctypes.c_uint,))
+    nativefunc(shader)
+
+
+# void = glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, GLvoid* data)
+def glCompressedTexImage2D(target, level, internalformat, width, height, border, data):
+    # border = 0  # set in args
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.size
+    data = data_.ctypes.data
+    try:
+        nativefunc = glCompressedTexImage2D._native
+    except AttributeError:
+        nativefunc = glCompressedTexImage2D._native = _get_gl_func("glCompressedTexImage2D", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_void_p,))
+    res = nativefunc(target, level, internalformat, width, height, border, imageSize, data)
+
+
+# void = glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, GLvoid* data)
+def glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, data):
+    if not data.flags['C_CONTIGUOUS']:
+        data = data.copy('C')
+    data_ = data
+    size = data_.size
+    data = data_.ctypes.data
+    try:
+        nativefunc = glCompressedTexSubImage2D._native
+    except AttributeError:
+        nativefunc = glCompressedTexSubImage2D._native = _get_gl_func("glCompressedTexSubImage2D", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_void_p,))
+    res = nativefunc(target, level, xoffset, yoffset, width, height, format, imageSize, data)
+
+
+# void = glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)
+def glCopyTexImage2D(target, level, internalformat, x, y, width, height, border):
+    try:
+        nativefunc = glCopyTexImage2D._native
+    except AttributeError:
+        nativefunc = glCopyTexImage2D._native = _get_gl_func("glCopyTexImage2D", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(target, level, internalformat, x, y, width, height, border)
+
+
+# void = glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
+def glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height):
+    try:
+        nativefunc = glCopyTexSubImage2D._native
+    except AttributeError:
+        nativefunc = glCopyTexSubImage2D._native = _get_gl_func("glCopyTexSubImage2D", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(target, level, xoffset, yoffset, x, y, width, height)
+
+
+# GLuint = glCreateProgram()
+def glCreateProgram():
+    try:
+        nativefunc = glCreateProgram._native
+    except AttributeError:
+        nativefunc = glCreateProgram._native = _get_gl_func("glCreateProgram", ctypes.c_uint, ())
+    return nativefunc()
+
+
+# GLuint = glCreateShader(GLenum type)
+def glCreateShader(type):
+    try:
+        nativefunc = glCreateShader._native
+    except AttributeError:
+        nativefunc = glCreateShader._native = _get_gl_func("glCreateShader", ctypes.c_uint, (ctypes.c_uint,))
+    return nativefunc(type)
+
+
+# void = glCullFace(GLenum mode)
+def glCullFace(mode):
+    try:
+        nativefunc = glCullFace._native
+    except AttributeError:
+        nativefunc = glCullFace._native = _get_gl_func("glCullFace", None, (ctypes.c_uint,))
+    nativefunc(mode)
+
+
+# void = glDeleteBuffers(GLsizei n, GLuint* buffers)
+def glDeleteBuffer(buffer):
+    n = 1
+    buffers = (ctypes.c_uint*n)(buffer)
+    try:
+        nativefunc = glDeleteBuffer._native
+    except AttributeError:
+        nativefunc = glDeleteBuffer._native = _get_gl_func("glDeleteBuffers", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, buffers)
+
+
+# void = glDeleteFramebuffers(GLsizei n, GLuint* framebuffers)
+def glDeleteFramebuffer(framebuffer):
+    n = 1
+    framebuffers = (ctypes.c_uint*n)(framebuffer)
+    try:
+        nativefunc = glDeleteFramebuffer._native
+    except AttributeError:
+        nativefunc = glDeleteFramebuffer._native = _get_gl_func("glDeleteFramebuffers", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, framebuffers)
+
+
+# void = glDeleteProgram(GLuint program)
+def glDeleteProgram(program):
+    try:
+        nativefunc = glDeleteProgram._native
+    except AttributeError:
+        nativefunc = glDeleteProgram._native = _get_gl_func("glDeleteProgram", None, (ctypes.c_uint,))
+    nativefunc(program)
+
+
+# void = glDeleteRenderbuffers(GLsizei n, GLuint* renderbuffers)
+def glDeleteRenderbuffer(renderbuffer):
+    n = 1
+    renderbuffers = (ctypes.c_uint*n)(renderbuffer)
+    try:
+        nativefunc = glDeleteRenderbuffer._native
+    except AttributeError:
+        nativefunc = glDeleteRenderbuffer._native = _get_gl_func("glDeleteRenderbuffers", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, renderbuffers)
+
+
+# void = glDeleteShader(GLuint shader)
+def glDeleteShader(shader):
+    try:
+        nativefunc = glDeleteShader._native
+    except AttributeError:
+        nativefunc = glDeleteShader._native = _get_gl_func("glDeleteShader", None, (ctypes.c_uint,))
+    nativefunc(shader)
+
+
+# void = glDeleteTextures(GLsizei n, GLuint* textures)
+def glDeleteTexture(texture):
+    n = 1
+    textures = (ctypes.c_uint*n)(texture)
+    try:
+        nativefunc = glDeleteTexture._native
+    except AttributeError:
+        nativefunc = glDeleteTexture._native = _get_gl_func("glDeleteTextures", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, textures)
+
+
+# void = glDepthFunc(GLenum func)
+def glDepthFunc(func):
+    try:
+        nativefunc = glDepthFunc._native
+    except AttributeError:
+        nativefunc = glDepthFunc._native = _get_gl_func("glDepthFunc", None, (ctypes.c_uint,))
+    nativefunc(func)
+
+
+# void = glDepthMask(GLboolean flag)
+def glDepthMask(flag):
+    try:
+        nativefunc = glDepthMask._native
+    except AttributeError:
+        nativefunc = glDepthMask._native = _get_gl_func("glDepthMask", None, (ctypes.c_bool,))
+    nativefunc(flag)
+
+
+# void = glDepthRangef(GLclampf zNear, GLclampf zFar)
+def glDepthRange(zNear, zFar):
+    try:
+        nativefunc = glDepthRange._native
+    except AttributeError:
+        nativefunc = glDepthRange._native = _get_gl_func("glDepthRange", None, (ctypes.c_double, ctypes.c_double,))
+    nativefunc(zNear, zFar)
+
+
+# void = glDetachShader(GLuint program, GLuint shader)
+def glDetachShader(program, shader):
+    try:
+        nativefunc = glDetachShader._native
+    except AttributeError:
+        nativefunc = glDetachShader._native = _get_gl_func("glDetachShader", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(program, shader)
+
+
+# void = glDisable(GLenum cap)
+def glDisable(cap):
+    try:
+        nativefunc = glDisable._native
+    except AttributeError:
+        nativefunc = glDisable._native = _get_gl_func("glDisable", None, (ctypes.c_uint,))
+    nativefunc(cap)
+
+
+# void = glDisableVertexAttribArray(GLuint index)
+def glDisableVertexAttribArray(index):
+    try:
+        nativefunc = glDisableVertexAttribArray._native
+    except AttributeError:
+        nativefunc = glDisableVertexAttribArray._native = _get_gl_func("glDisableVertexAttribArray", None, (ctypes.c_uint,))
+    nativefunc(index)
+
+
+# void = glDrawArrays(GLenum mode, GLint first, GLsizei count)
+def glDrawArrays(mode, first, count):
+    try:
+        nativefunc = glDrawArrays._native
+    except AttributeError:
+        nativefunc = glDrawArrays._native = _get_gl_func("glDrawArrays", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_int,))
+    nativefunc(mode, first, count)
+
+
+# void = glDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid* indices)
+def glDrawElements(mode, count, type, offset):
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, ctypes.c_void_p):
+        pass
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    else:
+        if not offset.flags['C_CONTIGUOUS']:
+            offset = offset.copy('C')
+        offset_ = offset
+        offset = offset.ctypes.data
+    indices = offset
+    try:
+        nativefunc = glDrawElements._native
+    except AttributeError:
+        nativefunc = glDrawElements._native = _get_gl_func("glDrawElements", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_void_p,))
+    res = nativefunc(mode, count, type, indices)
+
+
+# void = glEnable(GLenum cap)
+def glEnable(cap):
+    try:
+        nativefunc = glEnable._native
+    except AttributeError:
+        nativefunc = glEnable._native = _get_gl_func("glEnable", None, (ctypes.c_uint,))
+    nativefunc(cap)
+
+
+# void = glEnableVertexAttribArray(GLuint index)
+def glEnableVertexAttribArray(index):
+    try:
+        nativefunc = glEnableVertexAttribArray._native
+    except AttributeError:
+        nativefunc = glEnableVertexAttribArray._native = _get_gl_func("glEnableVertexAttribArray", None, (ctypes.c_uint,))
+    nativefunc(index)
+
+
+# void = glFinish()
+def glFinish():
+    try:
+        nativefunc = glFinish._native
+    except AttributeError:
+        nativefunc = glFinish._native = _get_gl_func("glFinish", None, ())
+    nativefunc()
+
+
+# void = glFlush()
+def glFlush():
+    try:
+        nativefunc = glFlush._native
+    except AttributeError:
+        nativefunc = glFlush._native = _get_gl_func("glFlush", None, ())
+    nativefunc()
+
+
+# void = glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
+def glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer):
+    try:
+        nativefunc = glFramebufferRenderbuffer._native
+    except AttributeError:
+        nativefunc = glFramebufferRenderbuffer._native = _get_gl_func("glFramebufferRenderbuffer", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(target, attachment, renderbuffertarget, renderbuffer)
+
+
+# void = glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)
+def glFramebufferTexture2D(target, attachment, textarget, texture, level):
+    try:
+        nativefunc = glFramebufferTexture2D._native
+    except AttributeError:
+        nativefunc = glFramebufferTexture2D._native = _get_gl_func("glFramebufferTexture2D", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_int,))
+    nativefunc(target, attachment, textarget, texture, level)
+
+
+# void = glFrontFace(GLenum mode)
+def glFrontFace(mode):
+    try:
+        nativefunc = glFrontFace._native
+    except AttributeError:
+        nativefunc = glFrontFace._native = _get_gl_func("glFrontFace", None, (ctypes.c_uint,))
+    nativefunc(mode)
+
+
+# void = glGenBuffers(GLsizei n, GLuint* buffers)
+def glCreateBuffer():
+    n = 1
+    buffers = (ctypes.c_uint*n)()
+    try:
+        nativefunc = glCreateBuffer._native
+    except AttributeError:
+        nativefunc = glCreateBuffer._native = _get_gl_func("glGenBuffers", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, buffers)
+    return buffers[0]
+
+
+# void = glGenFramebuffers(GLsizei n, GLuint* framebuffers)
+def glCreateFramebuffer():
+    n = 1
+    framebuffers = (ctypes.c_uint*n)()
+    try:
+        nativefunc = glCreateFramebuffer._native
+    except AttributeError:
+        nativefunc = glCreateFramebuffer._native = _get_gl_func("glGenFramebuffers", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, framebuffers)
+    return framebuffers[0]
+
+
+# void = glGenRenderbuffers(GLsizei n, GLuint* renderbuffers)
+def glCreateRenderbuffer():
+    n = 1
+    renderbuffers = (ctypes.c_uint*n)()
+    try:
+        nativefunc = glCreateRenderbuffer._native
+    except AttributeError:
+        nativefunc = glCreateRenderbuffer._native = _get_gl_func("glGenRenderbuffers", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, renderbuffers)
+    return renderbuffers[0]
+
+
+# void = glGenTextures(GLsizei n, GLuint* textures)
+def glCreateTexture():
+    n = 1
+    textures = (ctypes.c_uint*n)()
+    try:
+        nativefunc = glCreateTexture._native
+    except AttributeError:
+        nativefunc = glCreateTexture._native = _get_gl_func("glGenTextures", None, (ctypes.c_int, ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(n, textures)
+    return textures[0]
+
+
+# void = glGenerateMipmap(GLenum target)
+def glGenerateMipmap(target):
+    try:
+        nativefunc = glGenerateMipmap._native
+    except AttributeError:
+        nativefunc = glGenerateMipmap._native = _get_gl_func("glGenerateMipmap", None, (ctypes.c_uint,))
+    nativefunc(target)
+
+
+# void = glGetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, GLchar* name)
+def glGetActiveAttrib(program, index):
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    try:
+        nativefunc = glGetActiveAttrib._native
+    except AttributeError:
+        nativefunc = glGetActiveAttrib._native = _get_gl_func("glGetActiveAttrib", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint), ctypes.c_char_p,))
+    res = nativefunc(program, index, bufsize, length, size, type, name)
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+
+
+# void = glGetActiveUniform(GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, GLchar* name)
+def glGetActiveUniform(program, index):
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    try:
+        nativefunc = glGetActiveUniform._native
+    except AttributeError:
+        nativefunc = glGetActiveUniform._native = _get_gl_func("glGetActiveUniform", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint), ctypes.c_char_p,))
+    res = nativefunc(program, index, bufsize, length, size, type, name)
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+
+
+# void = glGetAttachedShaders(GLuint program, GLsizei maxcount, GLsizei* count, GLuint* shaders)
+def glGetAttachedShaders(program):
+    maxcount = 256
+    count = (ctypes.c_int*1)()
+    shaders = (ctypes.c_uint*maxcount)()
+    try:
+        nativefunc = glGetAttachedShaders._native
+    except AttributeError:
+        nativefunc = glGetAttachedShaders._native = _get_gl_func("glGetAttachedShaders", None, (ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_uint),))
+    res = nativefunc(program, maxcount, count, shaders)
+    return tuple(shaders[:count[0]])
+
+
+# GLint = glGetAttribLocation(GLuint program, GLchar* name)
+def glGetAttribLocation(program, name):
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    try:
+        nativefunc = glGetAttribLocation._native
+    except AttributeError:
+        nativefunc = glGetAttribLocation._native = _get_gl_func("glGetAttribLocation", ctypes.c_int, (ctypes.c_uint, ctypes.c_char_p,))
+    res = nativefunc(program, name)
+    return res
+
+
+# void = glGetBooleanv(GLenum pname, GLboolean* params)
+def _glGetBooleanv(pname):
+    params = (ctypes.c_bool*1)()
+    try:
+        nativefunc = _glGetBooleanv._native
+    except AttributeError:
+        nativefunc = _glGetBooleanv._native = _get_gl_func("glGetBooleanv", None, (ctypes.c_uint, ctypes.POINTER(ctypes.c_bool),))
+    res = nativefunc(pname, params)
+    return params[0]
+
+
+# void = glGetBufferParameteriv(GLenum target, GLenum pname, GLint* params)
+def glGetBufferParameter(target, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    try:
+        nativefunc = glGetBufferParameter._native
+    except AttributeError:
+        nativefunc = glGetBufferParameter._native = _get_gl_func("glGetBufferParameteriv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(target, pname, params)
+    return params[0]
+
+
+# GLenum = glGetError()
+def glGetError():
+    try:
+        nativefunc = glGetError._native
+    except AttributeError:
+        nativefunc = glGetError._native = _get_gl_func("glGetError", ctypes.c_uint, ())
+    return nativefunc()
+
+
+# void = glGetFloatv(GLenum pname, GLfloat* params)
+def _glGetFloatv(pname):
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    try:
+        nativefunc = _glGetFloatv._native
+    except AttributeError:
+        nativefunc = _glGetFloatv._native = _get_gl_func("glGetFloatv", None, (ctypes.c_uint, ctypes.POINTER(ctypes.c_float),))
+    res = nativefunc(pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+# void = glGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint* params)
+def glGetFramebufferAttachmentParameter(target, attachment, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    try:
+        nativefunc = glGetFramebufferAttachmentParameter._native
+    except AttributeError:
+        nativefunc = glGetFramebufferAttachmentParameter._native = _get_gl_func("glGetFramebufferAttachmentParameteriv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(target, attachment, pname, params)
+    return params[0]
+
+
+# void = glGetIntegerv(GLenum pname, GLint* params)
+def _glGetIntegerv(pname):
+    n = 16
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*n)(*[d for i in range(n)])
+    try:
+        nativefunc = _glGetIntegerv._native
+    except AttributeError:
+        nativefunc = _glGetIntegerv._native = _get_gl_func("glGetIntegerv", None, (ctypes.c_uint, ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+# void = glGetProgramInfoLog(GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog)
+def glGetProgramInfoLog(program):
+    bufsize = 1024
+    length = (ctypes.c_int*1)()
+    infolog = ctypes.create_string_buffer(bufsize)
+    try:
+        nativefunc = glGetProgramInfoLog._native
+    except AttributeError:
+        nativefunc = glGetProgramInfoLog._native = _get_gl_func("glGetProgramInfoLog", None, (ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p,))
+    res = nativefunc(program, bufsize, length, infolog)
+    return infolog[:length[0]].decode('utf-8')
+
+
+# void = glGetProgramiv(GLuint program, GLenum pname, GLint* params)
+def glGetProgramParameter(program, pname):
+    params = (ctypes.c_int*1)()
+    try:
+        nativefunc = glGetProgramParameter._native
+    except AttributeError:
+        nativefunc = glGetProgramParameter._native = _get_gl_func("glGetProgramiv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(program, pname, params)
+    return params[0]
+
+
+# void = glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params)
+def glGetRenderbufferParameter(target, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    try:
+        nativefunc = glGetRenderbufferParameter._native
+    except AttributeError:
+        nativefunc = glGetRenderbufferParameter._native = _get_gl_func("glGetRenderbufferParameteriv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(target, pname, params)
+    return params[0]
+
+
+# void = glGetShaderInfoLog(GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* infolog)
+def glGetShaderInfoLog(shader):
+    bufsize = 1024
+    length = (ctypes.c_int*1)()
+    infolog = ctypes.create_string_buffer(bufsize)
+    try:
+        nativefunc = glGetShaderInfoLog._native
+    except AttributeError:
+        nativefunc = glGetShaderInfoLog._native = _get_gl_func("glGetShaderInfoLog", None, (ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p,))
+    res = nativefunc(shader, bufsize, length, infolog)
+    return infolog[:length[0]].decode('utf-8')
+
+
+# void = glGetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision)
+def glGetShaderPrecisionFormat(shadertype, precisiontype):
+    range = (ctypes.c_int*1)()
+    precision = (ctypes.c_int*1)()
+    try:
+        nativefunc = glGetShaderPrecisionFormat._native
+    except AttributeError:
+        nativefunc = glGetShaderPrecisionFormat._native = _get_gl_func("glGetShaderPrecisionFormat", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(shadertype, precisiontype, range, precision)
+    return range[0], precision[0]
+
+
+# void = glGetShaderSource(GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* source)
+def glGetShaderSource(shader):
+    bufsize = 1024*1024
+    length = (ctypes.c_int*1)()
+    source = (ctypes.c_char*bufsize)()
+    try:
+        nativefunc = glGetShaderSource._native
+    except AttributeError:
+        nativefunc = glGetShaderSource._native = _get_gl_func("glGetShaderSource", None, (ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.c_char_p,))
+    res = nativefunc(shader, bufsize, length, source)
+    return source.value[:length[0]].decode('utf-8')
+
+
+# void = glGetShaderiv(GLuint shader, GLenum pname, GLint* params)
+def glGetShaderParameter(shader, pname):
+    params = (ctypes.c_int*1)()
+    try:
+        nativefunc = glGetShaderParameter._native
+    except AttributeError:
+        nativefunc = glGetShaderParameter._native = _get_gl_func("glGetShaderiv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(shader, pname, params)
+    return params[0]
+
+
+# GLubyte* = glGetString(GLenum name)
+def glGetParameter(pname):
+    if pname in [33902, 33901, 32773, 3106, 2931, 2928,
+                 2849, 32824, 10752, 32938]:
+        # GL_ALIASED_LINE_WIDTH_RANGE GL_ALIASED_POINT_SIZE_RANGE
+        # GL_BLEND_COLOR GL_COLOR_CLEAR_VALUE GL_DEPTH_CLEAR_VALUE
+        # GL_DEPTH_RANGE GL_LINE_WIDTH GL_POLYGON_OFFSET_FACTOR
+        # GL_POLYGON_OFFSET_UNITS GL_SAMPLE_COVERAGE_VALUE
+        return _glGetFloatv(pname)
+    elif pname in [7936, 7937, 7938, 35724, 7939]:
+        # GL_VENDOR, GL_RENDERER, GL_VERSION, GL_SHADING_LANGUAGE_VERSION,
+        # GL_EXTENSIONS are strings
+        pass  # string handled below
+    else:
+        return _glGetIntegerv(pname)
+    name = pname
+    try:
+        nativefunc = glGetParameter._native
+    except AttributeError:
+        nativefunc = glGetParameter._native = _get_gl_func("glGetString", ctypes.c_char_p, (ctypes.c_uint,))
+    res = nativefunc(name)
+    return res.decode('utf-8') if res else ''
+
+
+# void = glGetTexParameterfv(GLenum target, GLenum pname, GLfloat* params)
+def glGetTexParameter(target, pname):
+    d = float('Inf')
+    params = (ctypes.c_float*1)(d)
+    try:
+        nativefunc = glGetTexParameter._native
+    except AttributeError:
+        nativefunc = glGetTexParameter._native = _get_gl_func("glGetTexParameterfv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_float),))
+    res = nativefunc(target, pname, params)
+    return params[0]
+
+
+# void = glGetUniformfv(GLuint program, GLint location, GLfloat* params)
+def glGetUniform(program, location):
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    try:
+        nativefunc = glGetUniform._native
+    except AttributeError:
+        nativefunc = glGetUniform._native = _get_gl_func("glGetUniformfv", None, (ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_float),))
+    res = nativefunc(program, location, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+# GLint = glGetUniformLocation(GLuint program, GLchar* name)
+def glGetUniformLocation(program, name):
+    name = ctypes.c_char_p(name.encode('utf-8'))
+    try:
+        nativefunc = glGetUniformLocation._native
+    except AttributeError:
+        nativefunc = glGetUniformLocation._native = _get_gl_func("glGetUniformLocation", ctypes.c_int, (ctypes.c_uint, ctypes.c_char_p,))
+    res = nativefunc(program, name)
+    return res
+
+
+# void = glGetVertexAttribfv(GLuint index, GLenum pname, GLfloat* params)
+def glGetVertexAttrib(index, pname):
+    n = 4
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    try:
+        nativefunc = glGetVertexAttrib._native
+    except AttributeError:
+        nativefunc = glGetVertexAttrib._native = _get_gl_func("glGetVertexAttribfv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_float),))
+    res = nativefunc(index, pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+# void = glGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid** pointer)
+def glGetVertexAttribOffset(index, pname):
+    pointer = (ctypes.c_void_p*1)()
+    try:
+        nativefunc = glGetVertexAttribOffset._native
+    except AttributeError:
+        nativefunc = glGetVertexAttribOffset._native = _get_gl_func("glGetVertexAttribPointerv", None, (ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_void_p),))
+    res = nativefunc(index, pname, pointer)
+    return pointer[0] or 0
+
+
+# void = glHint(GLenum target, GLenum mode)
+def glHint(target, mode):
+    try:
+        nativefunc = glHint._native
+    except AttributeError:
+        nativefunc = glHint._native = _get_gl_func("glHint", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(target, mode)
+
+
+# GLboolean = glIsBuffer(GLuint buffer)
+def glIsBuffer(buffer):
+    try:
+        nativefunc = glIsBuffer._native
+    except AttributeError:
+        nativefunc = glIsBuffer._native = _get_gl_func("glIsBuffer", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(buffer)
+
+
+# GLboolean = glIsEnabled(GLenum cap)
+def glIsEnabled(cap):
+    try:
+        nativefunc = glIsEnabled._native
+    except AttributeError:
+        nativefunc = glIsEnabled._native = _get_gl_func("glIsEnabled", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(cap)
+
+
+# GLboolean = glIsFramebuffer(GLuint framebuffer)
+def glIsFramebuffer(framebuffer):
+    try:
+        nativefunc = glIsFramebuffer._native
+    except AttributeError:
+        nativefunc = glIsFramebuffer._native = _get_gl_func("glIsFramebuffer", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(framebuffer)
+
+
+# GLboolean = glIsProgram(GLuint program)
+def glIsProgram(program):
+    try:
+        nativefunc = glIsProgram._native
+    except AttributeError:
+        nativefunc = glIsProgram._native = _get_gl_func("glIsProgram", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(program)
+
+
+# GLboolean = glIsRenderbuffer(GLuint renderbuffer)
+def glIsRenderbuffer(renderbuffer):
+    try:
+        nativefunc = glIsRenderbuffer._native
+    except AttributeError:
+        nativefunc = glIsRenderbuffer._native = _get_gl_func("glIsRenderbuffer", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(renderbuffer)
+
+
+# GLboolean = glIsShader(GLuint shader)
+def glIsShader(shader):
+    try:
+        nativefunc = glIsShader._native
+    except AttributeError:
+        nativefunc = glIsShader._native = _get_gl_func("glIsShader", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(shader)
+
+
+# GLboolean = glIsTexture(GLuint texture)
+def glIsTexture(texture):
+    try:
+        nativefunc = glIsTexture._native
+    except AttributeError:
+        nativefunc = glIsTexture._native = _get_gl_func("glIsTexture", ctypes.c_bool, (ctypes.c_uint,))
+    return nativefunc(texture)
+
+
+# void = glLineWidth(GLfloat width)
+def glLineWidth(width):
+    try:
+        nativefunc = glLineWidth._native
+    except AttributeError:
+        nativefunc = glLineWidth._native = _get_gl_func("glLineWidth", None, (ctypes.c_float,))
+    nativefunc(width)
+
+
+# void = glLinkProgram(GLuint program)
+def glLinkProgram(program):
+    try:
+        nativefunc = glLinkProgram._native
+    except AttributeError:
+        nativefunc = glLinkProgram._native = _get_gl_func("glLinkProgram", None, (ctypes.c_uint,))
+    nativefunc(program)
+
+
+# void = glPixelStorei(GLenum pname, GLint param)
+def glPixelStorei(pname, param):
+    try:
+        nativefunc = glPixelStorei._native
+    except AttributeError:
+        nativefunc = glPixelStorei._native = _get_gl_func("glPixelStorei", None, (ctypes.c_uint, ctypes.c_int,))
+    nativefunc(pname, param)
+
+
+# void = glPolygonOffset(GLfloat factor, GLfloat units)
+def glPolygonOffset(factor, units):
+    try:
+        nativefunc = glPolygonOffset._native
+    except AttributeError:
+        nativefunc = glPolygonOffset._native = _get_gl_func("glPolygonOffset", None, (ctypes.c_float, ctypes.c_float,))
+    nativefunc(factor, units)
+
+
+# void = glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
+def glReadPixels(x, y, width, height, format, type):
+    # GL_ALPHA, GL_RGB, GL_RGBA
+    t = {6406:1, 6407:3, 6408:4}[format]
+    # we kind of only support type GL_UNSIGNED_BYTE
+    size = int(width*height*t)
+    pixels = ctypes.create_string_buffer(size)
+    try:
+        nativefunc = glReadPixels._native
+    except AttributeError:
+        nativefunc = glReadPixels._native = _get_gl_func("glReadPixels", None, (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p,))
+    res = nativefunc(x, y, width, height, format, type, pixels)
+    return pixels[:]
+
+
+# void = glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
+def glRenderbufferStorage(target, internalformat, width, height):
+    try:
+        nativefunc = glRenderbufferStorage._native
+    except AttributeError:
+        nativefunc = glRenderbufferStorage._native = _get_gl_func("glRenderbufferStorage", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.c_int,))
+    nativefunc(target, internalformat, width, height)
+
+
+# void = glSampleCoverage(GLclampf value, GLboolean invert)
+def glSampleCoverage(value, invert):
+    try:
+        nativefunc = glSampleCoverage._native
+    except AttributeError:
+        nativefunc = glSampleCoverage._native = _get_gl_func("glSampleCoverage", None, (ctypes.c_float, ctypes.c_bool,))
+    nativefunc(value, invert)
+
+
+# void = glScissor(GLint x, GLint y, GLsizei width, GLsizei height)
+def glScissor(x, y, width, height):
+    try:
+        nativefunc = glScissor._native
+    except AttributeError:
+        nativefunc = glScissor._native = _get_gl_func("glScissor", None, (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(x, y, width, height)
+
+
+# void = glShaderSource(GLuint shader, GLsizei count, GLchar** string, GLint* length)
+def glShaderSource(shader, source):
+    # Some implementation do not like getting a list of single chars
+    if isinstance(source, (tuple, list)):
+        strings = [s for s in source]
+    else:
+        strings = [source]
+    count = len(strings)
+    string = (ctypes.c_char_p*count)(*[s.encode('utf-8') for s in strings])
+    length = (ctypes.c_int*count)(*[len(s) for s in strings])
+    try:
+        nativefunc = glShaderSource._native
+    except AttributeError:
+        nativefunc = glShaderSource._native = _get_gl_func("glShaderSource", None, (ctypes.c_uint, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_int),))
+    res = nativefunc(shader, count, string, length)
+
+
+# void = glStencilFunc(GLenum func, GLint ref, GLuint mask)
+def glStencilFunc(func, ref, mask):
+    try:
+        nativefunc = glStencilFunc._native
+    except AttributeError:
+        nativefunc = glStencilFunc._native = _get_gl_func("glStencilFunc", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_uint,))
+    nativefunc(func, ref, mask)
+
+
+# void = glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask)
+def glStencilFuncSeparate(face, func, ref, mask):
+    try:
+        nativefunc = glStencilFuncSeparate._native
+    except AttributeError:
+        nativefunc = glStencilFuncSeparate._native = _get_gl_func("glStencilFuncSeparate", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_int, ctypes.c_uint,))
+    nativefunc(face, func, ref, mask)
+
+
+# void = glStencilMask(GLuint mask)
+def glStencilMask(mask):
+    try:
+        nativefunc = glStencilMask._native
+    except AttributeError:
+        nativefunc = glStencilMask._native = _get_gl_func("glStencilMask", None, (ctypes.c_uint,))
+    nativefunc(mask)
+
+
+# void = glStencilMaskSeparate(GLenum face, GLuint mask)
+def glStencilMaskSeparate(face, mask):
+    try:
+        nativefunc = glStencilMaskSeparate._native
+    except AttributeError:
+        nativefunc = glStencilMaskSeparate._native = _get_gl_func("glStencilMaskSeparate", None, (ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(face, mask)
+
+
+# void = glStencilOp(GLenum fail, GLenum zfail, GLenum zpass)
+def glStencilOp(fail, zfail, zpass):
+    try:
+        nativefunc = glStencilOp._native
+    except AttributeError:
+        nativefunc = glStencilOp._native = _get_gl_func("glStencilOp", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(fail, zfail, zpass)
+
+
+# void = glStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass)
+def glStencilOpSeparate(face, fail, zfail, zpass):
+    try:
+        nativefunc = glStencilOpSeparate._native
+    except AttributeError:
+        nativefunc = glStencilOpSeparate._native = _get_gl_func("glStencilOpSeparate", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,))
+    nativefunc(face, fail, zfail, zpass)
+
+
+# void = glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLvoid* pixels)
+def glTexImage2D(target, level, internalformat, format, type, pixels):
+    border = 0
+    if isinstance(pixels, (tuple, list)):
+        height, width = pixels
+        pixels = ctypes.c_void_p(0)
+        pixels = None
+    else:
+        if not pixels.flags['C_CONTIGUOUS']:
+            pixels = pixels.copy('C')
+        pixels_ = pixels
+        pixels = pixels_.ctypes.data
+        height, width = pixels_.shape[:2]
+    try:
+        nativefunc = glTexImage2D._native
+    except AttributeError:
+        nativefunc = glTexImage2D._native = _get_gl_func("glTexImage2D", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p,))
+    res = nativefunc(target, level, internalformat, width, height, border, format, type, pixels)
+
+
+def glTexParameterf(target, pname, param):
+    try:
+        nativefunc = glTexParameterf._native
+    except AttributeError:
+        nativefunc = glTexParameterf._native = _get_gl_func("glTexParameterf", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_float,))
+    nativefunc(target, pname, param)
+def glTexParameteri(target, pname, param):
+    try:
+        nativefunc = glTexParameteri._native
+    except AttributeError:
+        nativefunc = glTexParameteri._native = _get_gl_func("glTexParameteri", None, (ctypes.c_uint, ctypes.c_uint, ctypes.c_int,))
+    nativefunc(target, pname, param)
+
+
+# void = glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
+def glTexSubImage2D(target, level, xoffset, yoffset, format, type, pixels):
+    if not pixels.flags['C_CONTIGUOUS']:
+        pixels = pixels.copy('C')
+    pixels_ = pixels
+    pixels = pixels_.ctypes.data
+    height, width = pixels_.shape[:2]
+    try:
+        nativefunc = glTexSubImage2D._native
+    except AttributeError:
+        nativefunc = glTexSubImage2D._native = _get_gl_func("glTexSubImage2D", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p,))
+    res = nativefunc(target, level, xoffset, yoffset, width, height, format, type, pixels)
+
+
+def glUniform1f(location, v1):
+    try:
+        nativefunc = glUniform1f._native
+    except AttributeError:
+        nativefunc = glUniform1f._native = _get_gl_func("glUniform1f", None, (ctypes.c_int, ctypes.c_float,))
+    nativefunc(location, v1)
+def glUniform2f(location, v1, v2):
+    try:
+        nativefunc = glUniform2f._native
+    except AttributeError:
+        nativefunc = glUniform2f._native = _get_gl_func("glUniform2f", None, (ctypes.c_int, ctypes.c_float, ctypes.c_float,))
+    nativefunc(location, v1, v2)
+def glUniform3f(location, v1, v2, v3):
+    try:
+        nativefunc = glUniform3f._native
+    except AttributeError:
+        nativefunc = glUniform3f._native = _get_gl_func("glUniform3f", None, (ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float,))
+    nativefunc(location, v1, v2, v3)
+def glUniform4f(location, v1, v2, v3, v4):
+    try:
+        nativefunc = glUniform4f._native
+    except AttributeError:
+        nativefunc = glUniform4f._native = _get_gl_func("glUniform4f", None, (ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,))
+    nativefunc(location, v1, v2, v3, v4)
+def glUniform1i(location, v1):
+    try:
+        nativefunc = glUniform1i._native
+    except AttributeError:
+        nativefunc = glUniform1i._native = _get_gl_func("glUniform1i", None, (ctypes.c_int, ctypes.c_int,))
+    nativefunc(location, v1)
+def glUniform2i(location, v1, v2):
+    try:
+        nativefunc = glUniform2i._native
+    except AttributeError:
+        nativefunc = glUniform2i._native = _get_gl_func("glUniform2i", None, (ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(location, v1, v2)
+def glUniform3i(location, v1, v2, v3):
+    try:
+        nativefunc = glUniform3i._native
+    except AttributeError:
+        nativefunc = glUniform3i._native = _get_gl_func("glUniform3i", None, (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(location, v1, v2, v3)
+def glUniform4i(location, v1, v2, v3, v4):
+    try:
+        nativefunc = glUniform4i._native
+    except AttributeError:
+        nativefunc = glUniform4i._native = _get_gl_func("glUniform4i", None, (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(location, v1, v2, v3, v4)
+def glUniform1fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    try:
+        nativefunc = glUniform1fv._native
+    except AttributeError:
+        nativefunc = glUniform1fv._native = _get_gl_func("glUniform1fv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, values)
+def glUniform2fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    try:
+        nativefunc = glUniform2fv._native
+    except AttributeError:
+        nativefunc = glUniform2fv._native = _get_gl_func("glUniform2fv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, values)
+def glUniform3fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    try:
+        nativefunc = glUniform3fv._native
+    except AttributeError:
+        nativefunc = glUniform3fv._native = _get_gl_func("glUniform3fv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, values)
+def glUniform4fv(location, count, values):
+    values = [float(val) for val in values]
+    values = (ctypes.c_float*len(values))(*values)
+    try:
+        nativefunc = glUniform4fv._native
+    except AttributeError:
+        nativefunc = glUniform4fv._native = _get_gl_func("glUniform4fv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, values)
+def glUniform1iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    try:
+        nativefunc = glUniform1iv._native
+    except AttributeError:
+        nativefunc = glUniform1iv._native = _get_gl_func("glUniform1iv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),))
+    nativefunc(location, count, values)
+def glUniform2iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    try:
+        nativefunc = glUniform2iv._native
+    except AttributeError:
+        nativefunc = glUniform2iv._native = _get_gl_func("glUniform2iv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),))
+    nativefunc(location, count, values)
+def glUniform3iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    try:
+        nativefunc = glUniform3iv._native
+    except AttributeError:
+        nativefunc = glUniform3iv._native = _get_gl_func("glUniform3iv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),))
+    nativefunc(location, count, values)
+def glUniform4iv(location, count, values):
+    values = [int(val) for val in values]
+    values = (ctypes.c_int*len(values))(*values)
+    try:
+        nativefunc = glUniform4iv._native
+    except AttributeError:
+        nativefunc = glUniform4iv._native = _get_gl_func("glUniform4iv", None, (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),))
+    nativefunc(location, count, values)
+
+
+def glUniformMatrix2fv(location, count, transpose, values):
+    if not values.flags["C_CONTIGUOUS"]:
+        values = values.copy()
+    assert values.dtype.name == "float32"
+    values_ = values
+    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
+    try:
+        nativefunc = glUniformMatrix2fv._native
+    except AttributeError:
+        nativefunc = glUniformMatrix2fv._native = _get_gl_func("glUniformMatrix2fv", None, (ctypes.c_int, ctypes.c_int, ctypes.c_bool, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, transpose, values)
+def glUniformMatrix3fv(location, count, transpose, values):
+    if not values.flags["C_CONTIGUOUS"]:
+        values = values.copy()
+    assert values.dtype.name == "float32"
+    values_ = values
+    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
+    try:
+        nativefunc = glUniformMatrix3fv._native
+    except AttributeError:
+        nativefunc = glUniformMatrix3fv._native = _get_gl_func("glUniformMatrix3fv", None, (ctypes.c_int, ctypes.c_int, ctypes.c_bool, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, transpose, values)
+def glUniformMatrix4fv(location, count, transpose, values):
+    if not values.flags["C_CONTIGUOUS"]:
+        values = values.copy()
+    assert values.dtype.name == "float32"
+    values_ = values
+    values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
+    try:
+        nativefunc = glUniformMatrix4fv._native
+    except AttributeError:
+        nativefunc = glUniformMatrix4fv._native = _get_gl_func("glUniformMatrix4fv", None, (ctypes.c_int, ctypes.c_int, ctypes.c_bool, ctypes.POINTER(ctypes.c_float),))
+    nativefunc(location, count, transpose, values)
+
+
+# void = glUseProgram(GLuint program)
+def glUseProgram(program):
+    try:
+        nativefunc = glUseProgram._native
+    except AttributeError:
+        nativefunc = glUseProgram._native = _get_gl_func("glUseProgram", None, (ctypes.c_uint,))
+    nativefunc(program)
+
+
+# void = glValidateProgram(GLuint program)
+def glValidateProgram(program):
+    try:
+        nativefunc = glValidateProgram._native
+    except AttributeError:
+        nativefunc = glValidateProgram._native = _get_gl_func("glValidateProgram", None, (ctypes.c_uint,))
+    nativefunc(program)
+
+
+def glVertexAttrib1f(index, v1):
+    try:
+        nativefunc = glVertexAttrib1f._native
+    except AttributeError:
+        nativefunc = glVertexAttrib1f._native = _get_gl_func("glVertexAttrib1f", None, (ctypes.c_uint, ctypes.c_float,))
+    nativefunc(index, v1)
+def glVertexAttrib2f(index, v1, v2):
+    try:
+        nativefunc = glVertexAttrib2f._native
+    except AttributeError:
+        nativefunc = glVertexAttrib2f._native = _get_gl_func("glVertexAttrib2f", None, (ctypes.c_uint, ctypes.c_float, ctypes.c_float,))
+    nativefunc(index, v1, v2)
+def glVertexAttrib3f(index, v1, v2, v3):
+    try:
+        nativefunc = glVertexAttrib3f._native
+    except AttributeError:
+        nativefunc = glVertexAttrib3f._native = _get_gl_func("glVertexAttrib3f", None, (ctypes.c_uint, ctypes.c_float, ctypes.c_float, ctypes.c_float,))
+    nativefunc(index, v1, v2, v3)
+def glVertexAttrib4f(index, v1, v2, v3, v4):
+    try:
+        nativefunc = glVertexAttrib4f._native
+    except AttributeError:
+        nativefunc = glVertexAttrib4f._native = _get_gl_func("glVertexAttrib4f", None, (ctypes.c_uint, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float,))
+    nativefunc(index, v1, v2, v3, v4)
+
+
+# void = glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLvoid* ptr)
+def glVertexAttribPointer(indx, size, type, normalized, stride, offset):
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, ctypes.c_void_p):
+        pass
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    else:
+        if not offset.flags['C_CONTIGUOUS']:
+            offset = offset.copy('C')
+        offset_ = offset
+        offset = offset.ctypes.data
+        # We need to ensure that the data exists at draw time :(
+        # PyOpenGL does this too
+        key = '_vert_attr_'+str(indx)
+        setattr(glVertexAttribPointer, key, offset_)
+    ptr = offset
+    try:
+        nativefunc = glVertexAttribPointer._native
+    except AttributeError:
+        nativefunc = glVertexAttribPointer._native = _get_gl_func("glVertexAttribPointer", None, (ctypes.c_uint, ctypes.c_int, ctypes.c_uint, ctypes.c_bool, ctypes.c_int, ctypes.c_void_p,))
+    res = nativefunc(indx, size, type, normalized, stride, ptr)
+
+
+# void = glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
+def glViewport(x, y, width, height):
+    try:
+        nativefunc = glViewport._native
+    except AttributeError:
+        nativefunc = glViewport._native = _get_gl_func("glViewport", None, (ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int,))
+    nativefunc(x, y, width, height)
+
+
diff --git a/vispy/gloo/gl/_desktop_ext.py b/vispy/gloo/gl/_desktop_ext.py
deleted file mode 100644
index 970778f..0000000
--- a/vispy/gloo/gl/_desktop_ext.py
+++ /dev/null
@@ -1,30 +0,0 @@
-""" 
-
-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/_proxy.py b/vispy/gloo/gl/_proxy.py
new file mode 100644
index 0000000..9a89ea4
--- /dev/null
+++ b/vispy/gloo/gl/_proxy.py
@@ -0,0 +1,503 @@
+"""
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+Base proxy API for GL ES 2.0.
+
+"""
+
+class BaseGLProxy(object):
+    """ Base proxy class for the GL ES 2.0 API. Subclasses should
+    implement __call__ to process the API calls.
+    """
+   
+    def __call__(self, funcname, returns, *args):
+        raise NotImplementedError()
+    
+    
+    def glShaderSource_compat(self, handle, code):
+        return self("glShaderSource_compat", True, handle, code)
+
+
+    def glActiveTexture(self, texture):
+        self("glActiveTexture", False, texture)
+
+
+    def glAttachShader(self, program, shader):
+        self("glAttachShader", False, program, shader)
+
+
+    def glBindAttribLocation(self, program, index, name):
+        self("glBindAttribLocation", False, program, index, name)
+
+
+    def glBindBuffer(self, target, buffer):
+        self("glBindBuffer", False, target, buffer)
+
+
+    def glBindFramebuffer(self, target, framebuffer):
+        self("glBindFramebuffer", False, target, framebuffer)
+
+
+    def glBindRenderbuffer(self, target, renderbuffer):
+        self("glBindRenderbuffer", False, target, renderbuffer)
+
+
+    def glBindTexture(self, target, texture):
+        self("glBindTexture", False, target, texture)
+
+
+    def glBlendColor(self, red, green, blue, alpha):
+        self("glBlendColor", False, red, green, blue, alpha)
+
+
+    def glBlendEquation(self, mode):
+        self("glBlendEquation", False, mode)
+
+
+    def glBlendEquationSeparate(self, modeRGB, modeAlpha):
+        self("glBlendEquationSeparate", False, modeRGB, modeAlpha)
+
+
+    def glBlendFunc(self, sfactor, dfactor):
+        self("glBlendFunc", False, sfactor, dfactor)
+
+
+    def glBlendFuncSeparate(self, srcRGB, dstRGB, srcAlpha, dstAlpha):
+        self("glBlendFuncSeparate", False, srcRGB, dstRGB, srcAlpha, dstAlpha)
+
+
+    def glBufferData(self, target, data, usage):
+        self("glBufferData", False, target, data, usage)
+
+
+    def glBufferSubData(self, target, offset, data):
+        self("glBufferSubData", False, target, offset, data)
+
+
+    def glCheckFramebufferStatus(self, target):
+        return self("glCheckFramebufferStatus", True, target)
+
+
+    def glClear(self, mask):
+        self("glClear", False, mask)
+
+
+    def glClearColor(self, red, green, blue, alpha):
+        self("glClearColor", False, red, green, blue, alpha)
+
+
+    def glClearDepth(self, depth):
+        self("glClearDepth", False, depth)
+
+
+    def glClearStencil(self, s):
+        self("glClearStencil", False, s)
+
+
+    def glColorMask(self, red, green, blue, alpha):
+        self("glColorMask", False, red, green, blue, alpha)
+
+
+    def glCompileShader(self, shader):
+        self("glCompileShader", False, shader)
+
+
+    def glCompressedTexImage2D(self, target, level, internalformat, width, height, border, data):
+        self("glCompressedTexImage2D", False, target, level, internalformat, width, height, border, data)
+
+
+    def glCompressedTexSubImage2D(self, target, level, xoffset, yoffset, width, height, format, data):
+        self("glCompressedTexSubImage2D", False, target, level, xoffset, yoffset, width, height, format, data)
+
+
+    def glCopyTexImage2D(self, target, level, internalformat, x, y, width, height, border):
+        self("glCopyTexImage2D", False, target, level, internalformat, x, y, width, height, border)
+
+
+    def glCopyTexSubImage2D(self, target, level, xoffset, yoffset, x, y, width, height):
+        self("glCopyTexSubImage2D", False, target, level, xoffset, yoffset, x, y, width, height)
+
+
+    def glCreateProgram(self, ):
+        return self("glCreateProgram", True, )
+
+
+    def glCreateShader(self, type):
+        return self("glCreateShader", True, type)
+
+
+    def glCullFace(self, mode):
+        self("glCullFace", False, mode)
+
+
+    def glDeleteBuffer(self, buffer):
+        self("glDeleteBuffer", False, buffer)
+
+
+    def glDeleteFramebuffer(self, framebuffer):
+        self("glDeleteFramebuffer", False, framebuffer)
+
+
+    def glDeleteProgram(self, program):
+        self("glDeleteProgram", False, program)
+
+
+    def glDeleteRenderbuffer(self, renderbuffer):
+        self("glDeleteRenderbuffer", False, renderbuffer)
+
+
+    def glDeleteShader(self, shader):
+        self("glDeleteShader", False, shader)
+
+
+    def glDeleteTexture(self, texture):
+        self("glDeleteTexture", False, texture)
+
+
+    def glDepthFunc(self, func):
+        self("glDepthFunc", False, func)
+
+
+    def glDepthMask(self, flag):
+        self("glDepthMask", False, flag)
+
+
+    def glDepthRange(self, zNear, zFar):
+        self("glDepthRange", False, zNear, zFar)
+
+
+    def glDetachShader(self, program, shader):
+        self("glDetachShader", False, program, shader)
+
+
+    def glDisable(self, cap):
+        self("glDisable", False, cap)
+
+
+    def glDisableVertexAttribArray(self, index):
+        self("glDisableVertexAttribArray", False, index)
+
+
+    def glDrawArrays(self, mode, first, count):
+        self("glDrawArrays", False, mode, first, count)
+
+
+    def glDrawElements(self, mode, count, type, offset):
+        self("glDrawElements", False, mode, count, type, offset)
+
+
+    def glEnable(self, cap):
+        self("glEnable", False, cap)
+
+
+    def glEnableVertexAttribArray(self, index):
+        self("glEnableVertexAttribArray", False, index)
+
+
+    def glFinish(self, ):
+        self("glFinish", False, )
+
+
+    def glFlush(self, ):
+        self("glFlush", False, )
+
+
+    def glFramebufferRenderbuffer(self, target, attachment, renderbuffertarget, renderbuffer):
+        self("glFramebufferRenderbuffer", False, target, attachment, renderbuffertarget, renderbuffer)
+
+
+    def glFramebufferTexture2D(self, target, attachment, textarget, texture, level):
+        self("glFramebufferTexture2D", False, target, attachment, textarget, texture, level)
+
+
+    def glFrontFace(self, mode):
+        self("glFrontFace", False, mode)
+
+
+    def glCreateBuffer(self, ):
+        return self("glCreateBuffer", True, )
+
+
+    def glCreateFramebuffer(self, ):
+        return self("glCreateFramebuffer", True, )
+
+
+    def glCreateRenderbuffer(self, ):
+        return self("glCreateRenderbuffer", True, )
+
+
+    def glCreateTexture(self, ):
+        return self("glCreateTexture", True, )
+
+
+    def glGenerateMipmap(self, target):
+        self("glGenerateMipmap", False, target)
+
+
+    def glGetActiveAttrib(self, program, index):
+        return self("glGetActiveAttrib", True, program, index)
+
+
+    def glGetActiveUniform(self, program, index):
+        return self("glGetActiveUniform", True, program, index)
+
+
+    def glGetAttachedShaders(self, program):
+        return self("glGetAttachedShaders", True, program)
+
+
+    def glGetAttribLocation(self, program, name):
+        return self("glGetAttribLocation", True, program, name)
+
+
+    def _glGetBooleanv(self, pname):
+        self("_glGetBooleanv", False, pname)
+
+
+    def glGetBufferParameter(self, target, pname):
+        return self("glGetBufferParameter", True, target, pname)
+
+
+    def glGetError(self, ):
+        return self("glGetError", True, )
+
+
+    def _glGetFloatv(self, pname):
+        self("_glGetFloatv", False, pname)
+
+
+    def glGetFramebufferAttachmentParameter(self, target, attachment, pname):
+        return self("glGetFramebufferAttachmentParameter", True, target, attachment, pname)
+
+
+    def _glGetIntegerv(self, pname):
+        self("_glGetIntegerv", False, pname)
+
+
+    def glGetProgramInfoLog(self, program):
+        return self("glGetProgramInfoLog", True, program)
+
+
+    def glGetProgramParameter(self, program, pname):
+        return self("glGetProgramParameter", True, program, pname)
+
+
+    def glGetRenderbufferParameter(self, target, pname):
+        return self("glGetRenderbufferParameter", True, target, pname)
+
+
+    def glGetShaderInfoLog(self, shader):
+        return self("glGetShaderInfoLog", True, shader)
+
+
+    def glGetShaderPrecisionFormat(self, shadertype, precisiontype):
+        return self("glGetShaderPrecisionFormat", True, shadertype, precisiontype)
+
+
+    def glGetShaderSource(self, shader):
+        return self("glGetShaderSource", True, shader)
+
+
+    def glGetShaderParameter(self, shader, pname):
+        return self("glGetShaderParameter", True, shader, pname)
+
+
+    def glGetParameter(self, pname):
+        return self("glGetParameter", True, pname)
+
+
+    def glGetTexParameter(self, target, pname):
+        return self("glGetTexParameter", True, target, pname)
+
+
+    def glGetUniform(self, program, location):
+        return self("glGetUniform", True, program, location)
+
+
+    def glGetUniformLocation(self, program, name):
+        return self("glGetUniformLocation", True, program, name)
+
+
+    def glGetVertexAttrib(self, index, pname):
+        return self("glGetVertexAttrib", True, index, pname)
+
+
+    def glGetVertexAttribOffset(self, index, pname):
+        return self("glGetVertexAttribOffset", True, index, pname)
+
+
+    def glHint(self, target, mode):
+        self("glHint", False, target, mode)
+
+
+    def glIsBuffer(self, buffer):
+        return self("glIsBuffer", True, buffer)
+
+
+    def glIsEnabled(self, cap):
+        return self("glIsEnabled", True, cap)
+
+
+    def glIsFramebuffer(self, framebuffer):
+        return self("glIsFramebuffer", True, framebuffer)
+
+
+    def glIsProgram(self, program):
+        return self("glIsProgram", True, program)
+
+
+    def glIsRenderbuffer(self, renderbuffer):
+        return self("glIsRenderbuffer", True, renderbuffer)
+
+
+    def glIsShader(self, shader):
+        return self("glIsShader", True, shader)
+
+
+    def glIsTexture(self, texture):
+        return self("glIsTexture", True, texture)
+
+
+    def glLineWidth(self, width):
+        self("glLineWidth", False, width)
+
+
+    def glLinkProgram(self, program):
+        self("glLinkProgram", False, program)
+
+
+    def glPixelStorei(self, pname, param):
+        self("glPixelStorei", False, pname, param)
+
+
+    def glPolygonOffset(self, factor, units):
+        self("glPolygonOffset", False, factor, units)
+
+
+    def glReadPixels(self, x, y, width, height, format, type):
+        return self("glReadPixels", True, x, y, width, height, format, type)
+
+
+    def glRenderbufferStorage(self, target, internalformat, width, height):
+        self("glRenderbufferStorage", False, target, internalformat, width, height)
+
+
+    def glSampleCoverage(self, value, invert):
+        self("glSampleCoverage", False, value, invert)
+
+
+    def glScissor(self, x, y, width, height):
+        self("glScissor", False, x, y, width, height)
+
+
+    def glShaderSource(self, shader, source):
+        self("glShaderSource", False, shader, source)
+
+
+    def glStencilFunc(self, func, ref, mask):
+        self("glStencilFunc", False, func, ref, mask)
+
+
+    def glStencilFuncSeparate(self, face, func, ref, mask):
+        self("glStencilFuncSeparate", False, face, func, ref, mask)
+
+
+    def glStencilMask(self, mask):
+        self("glStencilMask", False, mask)
+
+
+    def glStencilMaskSeparate(self, face, mask):
+        self("glStencilMaskSeparate", False, face, mask)
+
+
+    def glStencilOp(self, fail, zfail, zpass):
+        self("glStencilOp", False, fail, zfail, zpass)
+
+
+    def glStencilOpSeparate(self, face, fail, zfail, zpass):
+        self("glStencilOpSeparate", False, face, fail, zfail, zpass)
+
+
+    def glTexImage2D(self, target, level, internalformat, format, type, pixels):
+        self("glTexImage2D", False, target, level, internalformat, format, type, pixels)
+
+
+    def glTexParameterf(self, target, pname, param):
+        self("glTexParameterf", False, target, pname, param)
+    def glTexParameteri(self, target, pname, param):
+        self("glTexParameteri", False, target, pname, param)
+
+
+    def glTexSubImage2D(self, target, level, xoffset, yoffset, format, type, pixels):
+        self("glTexSubImage2D", False, target, level, xoffset, yoffset, format, type, pixels)
+
+
+    def glUniform1f(self, location, v1):
+        self("glUniform1f", False, location, v1)
+    def glUniform2f(self, location, v1, v2):
+        self("glUniform2f", False, location, v1, v2)
+    def glUniform3f(self, location, v1, v2, v3):
+        self("glUniform3f", False, location, v1, v2, v3)
+    def glUniform4f(self, location, v1, v2, v3, v4):
+        self("glUniform4f", False, location, v1, v2, v3, v4)
+    def glUniform1i(self, location, v1):
+        self("glUniform1i", False, location, v1)
+    def glUniform2i(self, location, v1, v2):
+        self("glUniform2i", False, location, v1, v2)
+    def glUniform3i(self, location, v1, v2, v3):
+        self("glUniform3i", False, location, v1, v2, v3)
+    def glUniform4i(self, location, v1, v2, v3, v4):
+        self("glUniform4i", False, location, v1, v2, v3, v4)
+    def glUniform1fv(self, location, count, values):
+        self("glUniform1fv", False, location, count, values)
+    def glUniform2fv(self, location, count, values):
+        self("glUniform2fv", False, location, count, values)
+    def glUniform3fv(self, location, count, values):
+        self("glUniform3fv", False, location, count, values)
+    def glUniform4fv(self, location, count, values):
+        self("glUniform4fv", False, location, count, values)
+    def glUniform1iv(self, location, count, values):
+        self("glUniform1iv", False, location, count, values)
+    def glUniform2iv(self, location, count, values):
+        self("glUniform2iv", False, location, count, values)
+    def glUniform3iv(self, location, count, values):
+        self("glUniform3iv", False, location, count, values)
+    def glUniform4iv(self, location, count, values):
+        self("glUniform4iv", False, location, count, values)
+
+
+    def glUniformMatrix2fv(self, location, count, transpose, values):
+        self("glUniformMatrix2fv", False, location, count, transpose, values)
+    def glUniformMatrix3fv(self, location, count, transpose, values):
+        self("glUniformMatrix3fv", False, location, count, transpose, values)
+    def glUniformMatrix4fv(self, location, count, transpose, values):
+        self("glUniformMatrix4fv", False, location, count, transpose, values)
+
+
+    def glUseProgram(self, program):
+        self("glUseProgram", False, program)
+
+
+    def glValidateProgram(self, program):
+        self("glValidateProgram", False, program)
+
+
+    def glVertexAttrib1f(self, index, v1):
+        self("glVertexAttrib1f", False, index, v1)
+    def glVertexAttrib2f(self, index, v1, v2):
+        self("glVertexAttrib2f", False, index, v1, v2)
+    def glVertexAttrib3f(self, index, v1, v2, v3):
+        self("glVertexAttrib3f", False, index, v1, v2, v3)
+    def glVertexAttrib4f(self, index, v1, v2, v3, v4):
+        self("glVertexAttrib4f", False, index, v1, v2, v3, v4)
+
+
+    def glVertexAttribPointer(self, indx, size, type, normalized, stride, offset):
+        self("glVertexAttribPointer", False, indx, size, type, normalized, stride, offset)
+
+
+    def glViewport(self, x, y, width, height):
+        self("glViewport", False, x, y, width, height)
+
+
diff --git a/vispy/gloo/gl/_pyopengl.py b/vispy/gloo/gl/_pyopengl.py
new file mode 100644
index 0000000..d1f5133
--- /dev/null
+++ b/vispy/gloo/gl/_pyopengl.py
@@ -0,0 +1,335 @@
+"""
+
+THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
+
+Proxy API for GL ES 2.0 subset, via the PyOpenGL library.
+
+"""
+
+import ctypes
+from OpenGL import GL
+import OpenGL.GL.framebufferobjects as FBO
+
+
+
+
+
+def glBindAttribLocation(program, index, name):
+    name = name.encode('utf-8')
+    return GL.glBindAttribLocation(program, index, name)
+
+
+def glBufferData(target, data, usage):
+    """ Data can be numpy array or the size of data to allocate.
+    """
+    if isinstance(data, int):
+        size = data
+        data = None
+    else:
+        size = data.nbytes
+    GL.glBufferData(target, size, data, usage)
+
+
+def glBufferSubData(target, offset, data):
+    size = data.nbytes
+    GL.glBufferSubData(target, offset, size, data)
+
+
+def glCompressedTexImage2D(target, level, internalformat, width, height, border, data):
+    # border = 0  # set in args
+    size = data.size
+    GL.glCompressedTexImage2D(target, level, internalformat, width, height, border, size, data)
+
+
+def glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, data):
+    size = data.size
+    GL.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, size, data)
+
+
+def glDeleteBuffer(buffer):
+    GL.glDeleteBuffers(1, [buffer])
+
+
+def glDeleteFramebuffer(framebuffer):
+    FBO.glDeleteFramebuffers(1, [framebuffer])
+
+
+def glDeleteRenderbuffer(renderbuffer):
+    FBO.glDeleteRenderbuffers(1, [renderbuffer])
+
+
+def glDeleteTexture(texture):
+    GL.glDeleteTextures([texture])
+
+
+def glDrawElements(mode, count, type, offset):
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    return GL.glDrawElements(mode, count, type, offset)
+
+
+def glCreateBuffer():
+    return GL.glGenBuffers(1)
+
+
+def glCreateFramebuffer():
+    return FBO.glGenFramebuffers(1)
+
+
+def glCreateRenderbuffer():
+    return FBO.glGenRenderbuffers(1)
+
+
+def glCreateTexture():
+    return GL.glGenTextures(1)
+
+
+def glGetActiveAttrib(program, index):
+    bufsize = 256
+    length = (ctypes.c_int*1)()
+    size = (ctypes.c_int*1)()
+    type = (ctypes.c_uint*1)()
+    name = ctypes.create_string_buffer(bufsize)
+    # pyopengl has a bug, this is a patch
+    GL.glGetActiveAttrib(program, index, bufsize, length, size, type, name)
+    name = name[:length[0]].decode('utf-8')
+    return name, size[0], type[0]
+
+
+def glGetActiveUniform(program, index):
+    name, size, type = GL.glGetActiveUniform(program, index)
+    return name.decode('utf-8'), size, type
+
+
+def glGetAttribLocation(program, name):
+    name = name.encode('utf-8')
+    return GL.glGetAttribLocation(program, name)
+
+
+def glGetFramebufferAttachmentParameter(target, attachment, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    FBO.glGetFramebufferAttachmentParameteriv(target, attachment, pname, params)
+    return params[0]
+
+
+def glGetProgramInfoLog(program):
+    res = GL.glGetProgramInfoLog(program)
+    return res.decode('utf-8')
+
+
+def glGetRenderbufferParameter(target, pname):
+    d = -2**31  # smallest 32bit integer
+    params = (ctypes.c_int*1)(d)
+    FBO.glGetRenderbufferParameteriv(target, pname, params)
+    return params[0]
+
+
+def glGetShaderInfoLog(shader):
+    res = GL.glGetShaderInfoLog(shader)
+    return res.decode('utf-8')
+
+
+def glGetShaderSource(shader):
+    res = GL.glGetShaderSource(shader)
+    return res.decode('utf-8')
+
+
+def glGetParameter(pname):
+    if pname in [33902, 33901, 32773, 3106, 2931, 2928,
+                 2849, 32824, 10752, 32938]:
+        # GL_ALIASED_LINE_WIDTH_RANGE GL_ALIASED_POINT_SIZE_RANGE
+        # GL_BLEND_COLOR GL_COLOR_CLEAR_VALUE GL_DEPTH_CLEAR_VALUE
+        # GL_DEPTH_RANGE GL_LINE_WIDTH GL_POLYGON_OFFSET_FACTOR
+        # GL_POLYGON_OFFSET_UNITS GL_SAMPLE_COVERAGE_VALUE
+        return _glGetFloatv(pname)
+    elif pname in [7936, 7937, 7938, 35724, 7939]:
+        # GL_VENDOR, GL_RENDERER, GL_VERSION, GL_SHADING_LANGUAGE_VERSION,
+        # GL_EXTENSIONS are strings
+        pass  # string handled below
+    else:
+        return _glGetIntegerv(pname)
+    name = pname
+    res = GL.glGetString(pname)
+    return res.decode('utf-8')
+
+
+def glGetUniform(program, location):
+    n = 16
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    GL.glGetUniformfv(program, location, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+def glGetUniformLocation(program, name):
+    name = name.encode('utf-8')
+    return GL.glGetUniformLocation(program, name)
+
+
+def glGetVertexAttrib(index, pname):
+    # From PyOpenGL v3.1.0 the glGetVertexAttribfv(index, pname) does
+    # work, but it always returns 4 values, with zeros in the empty
+    # spaces. We have no way to tell whether they are empty or genuine
+    # zeros. Fortunately, pyopengl also supports the old syntax.
+    n = 4
+    d = float('Inf')
+    params = (ctypes.c_float*n)(*[d for i in range(n)])
+    GL.glGetVertexAttribfv(index, pname, params)
+    params = [p for p in params if p!=d]
+    if len(params) == 1:
+        return params[0]
+    else:
+        return tuple(params)
+
+
+def glGetVertexAttribOffset(index, pname):
+    try:  # maybe the fixed it
+        return GL.glGetVertexAttribPointerv(index, pname)
+    except TypeError:
+        pointer = (ctypes.c_void_p*1)()
+        GL.glGetVertexAttribPointerv(index, pname, pointer)
+        return pointer[0] or 0
+
+
+def glShaderSource(shader, source):
+    # Some implementation do not like getting a list of single chars
+    if isinstance(source, (tuple, list)):
+        strings = [s for s in source]
+    else:
+        strings = [source]
+    GL.glShaderSource(shader, strings)
+
+
+def glTexImage2D(target, level, internalformat, format, type, pixels):
+    border = 0
+    if isinstance(pixels, (tuple, list)):
+        height, width = pixels
+        pixels = None
+    else:
+        height, width = pixels.shape[:2]
+    GL.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
+
+
+def glTexSubImage2D(target, level, xoffset, yoffset, format, type, pixels):
+    height, width = pixels.shape[:2]
+    GL.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
+
+
+def glVertexAttribPointer(indx, size, type, normalized, stride, offset):
+    if offset is None:
+        offset = ctypes.c_void_p(0)
+    elif isinstance(offset, (int, ctypes.c_int)):
+        offset = ctypes.c_void_p(int(offset))
+    return GL.glVertexAttribPointer(indx, size, type, normalized, stride, offset)
+
+
+# List of functions that we should import from OpenGL.GL
+_functions_to_import = [
+    ("glActiveTexture", "glActiveTexture"),
+    ("glAttachShader", "glAttachShader"),
+    ("glBindBuffer", "glBindBuffer"),
+    ("glBindFramebuffer", "glBindFramebuffer"),
+    ("glBindRenderbuffer", "glBindRenderbuffer"),
+    ("glBindTexture", "glBindTexture"),
+    ("glBlendColor", "glBlendColor"),
+    ("glBlendEquation", "glBlendEquation"),
+    ("glBlendEquationSeparate", "glBlendEquationSeparate"),
+    ("glBlendFunc", "glBlendFunc"),
+    ("glBlendFuncSeparate", "glBlendFuncSeparate"),
+    ("glCheckFramebufferStatus", "glCheckFramebufferStatus"),
+    ("glClear", "glClear"),
+    ("glClearColor", "glClearColor"),
+    ("glClearDepthf", "glClearDepth"),
+    ("glClearStencil", "glClearStencil"),
+    ("glColorMask", "glColorMask"),
+    ("glCompileShader", "glCompileShader"),
+    ("glCopyTexImage2D", "glCopyTexImage2D"),
+    ("glCopyTexSubImage2D", "glCopyTexSubImage2D"),
+    ("glCreateProgram", "glCreateProgram"),
+    ("glCreateShader", "glCreateShader"),
+    ("glCullFace", "glCullFace"),
+    ("glDeleteProgram", "glDeleteProgram"),
+    ("glDeleteShader", "glDeleteShader"),
+    ("glDepthFunc", "glDepthFunc"),
+    ("glDepthMask", "glDepthMask"),
+    ("glDepthRangef", "glDepthRange"),
+    ("glDetachShader", "glDetachShader"),
+    ("glDisable", "glDisable"),
+    ("glDisableVertexAttribArray", "glDisableVertexAttribArray"),
+    ("glDrawArrays", "glDrawArrays"),
+    ("glEnable", "glEnable"),
+    ("glEnableVertexAttribArray", "glEnableVertexAttribArray"),
+    ("glFinish", "glFinish"),
+    ("glFlush", "glFlush"),
+    ("glFramebufferRenderbuffer", "glFramebufferRenderbuffer"),
+    ("glFramebufferTexture2D", "glFramebufferTexture2D"),
+    ("glFrontFace", "glFrontFace"),
+    ("glGenerateMipmap", "glGenerateMipmap"),
+    ("glGetAttachedShaders", "glGetAttachedShaders"),
+    ("glGetBooleanv", "_glGetBooleanv"),
+    ("glGetBufferParameteriv", "glGetBufferParameter"),
+    ("glGetError", "glGetError"),
+    ("glGetFloatv", "_glGetFloatv"),
+    ("glGetIntegerv", "_glGetIntegerv"),
+    ("glGetProgramiv", "glGetProgramParameter"),
+    ("glGetShaderPrecisionFormat", "glGetShaderPrecisionFormat"),
+    ("glGetShaderiv", "glGetShaderParameter"),
+    ("glGetTexParameterfv", "glGetTexParameter"),
+    ("glHint", "glHint"),
+    ("glIsBuffer", "glIsBuffer"),
+    ("glIsEnabled", "glIsEnabled"),
+    ("glIsFramebuffer", "glIsFramebuffer"),
+    ("glIsProgram", "glIsProgram"),
+    ("glIsRenderbuffer", "glIsRenderbuffer"),
+    ("glIsShader", "glIsShader"),
+    ("glIsTexture", "glIsTexture"),
+    ("glLineWidth", "glLineWidth"),
+    ("glLinkProgram", "glLinkProgram"),
+    ("glPixelStorei", "glPixelStorei"),
+    ("glPolygonOffset", "glPolygonOffset"),
+    ("glReadPixels", "glReadPixels"),
+    ("glRenderbufferStorage", "glRenderbufferStorage"),
+    ("glSampleCoverage", "glSampleCoverage"),
+    ("glScissor", "glScissor"),
+    ("glStencilFunc", "glStencilFunc"),
+    ("glStencilFuncSeparate", "glStencilFuncSeparate"),
+    ("glStencilMask", "glStencilMask"),
+    ("glStencilMaskSeparate", "glStencilMaskSeparate"),
+    ("glStencilOp", "glStencilOp"),
+    ("glStencilOpSeparate", "glStencilOpSeparate"),
+    ("glTexParameterf", "glTexParameterf"),
+    ("glTexParameteri", "glTexParameteri"),
+    ("glUniform1f", "glUniform1f"),
+    ("glUniform2f", "glUniform2f"),
+    ("glUniform3f", "glUniform3f"),
+    ("glUniform4f", "glUniform4f"),
+    ("glUniform1i", "glUniform1i"),
+    ("glUniform2i", "glUniform2i"),
+    ("glUniform3i", "glUniform3i"),
+    ("glUniform4i", "glUniform4i"),
+    ("glUniform1fv", "glUniform1fv"),
+    ("glUniform2fv", "glUniform2fv"),
+    ("glUniform3fv", "glUniform3fv"),
+    ("glUniform4fv", "glUniform4fv"),
+    ("glUniform1iv", "glUniform1iv"),
+    ("glUniform2iv", "glUniform2iv"),
+    ("glUniform3iv", "glUniform3iv"),
+    ("glUniform4iv", "glUniform4iv"),
+    ("glUniformMatrix2fv", "glUniformMatrix2fv"),
+    ("glUniformMatrix3fv", "glUniformMatrix3fv"),
+    ("glUniformMatrix4fv", "glUniformMatrix4fv"),
+    ("glUseProgram", "glUseProgram"),
+    ("glValidateProgram", "glValidateProgram"),
+    ("glVertexAttrib1f", "glVertexAttrib1f"),
+    ("glVertexAttrib2f", "glVertexAttrib2f"),
+    ("glVertexAttrib3f", "glVertexAttrib3f"),
+    ("glVertexAttrib4f", "glVertexAttrib4f"),
+    ("glViewport", "glViewport"),
+    ]
diff --git a/vispy/gloo/gl/angle.py b/vispy/gloo/gl/angle.py
new file mode 100644
index 0000000..e1e8f28
--- /dev/null
+++ b/vispy/gloo/gl/angle.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" GL ES 2.0 API implemented via Angle (i.e translated to DirectX).
+"""
+
+import ctypes
+
+from . import _copy_gl_functions
+from ._constants import *  # noqa
+
+
+## Ctypes stuff
+
+# todo: were are we going to put our libs?
+dirname = r'C:\Users\Almar\AppData\Local\Chromium\Application\34.0.1790.0'
+
+# Load dependency (so that libGLESv2 can find it
+fname = dirname + r'\d3dcompiler_46.dll'
+_libdum = ctypes.windll.LoadLibrary(fname)
+
+# Load GL ES 2.0 lib (Angle)
+fname = dirname + r'\libGLESv2.dll'
+_lib = ctypes.windll.LoadLibrary(fname)
+
+
+## Compatibility
+
+
+def glShaderSource_compat(handle, code):
+    """ Easy for real ES 2.0.
+    """
+    glShaderSource(handle, [code])
+    return []
+
+
+## Inject
+
+
+from . import _angle
+_copy_gl_functions(_angle, globals())
diff --git a/vispy/gloo/gl/desktop.py b/vispy/gloo/gl/desktop.py
index 7869ea3..c50d6b4 100644
--- a/vispy/gloo/gl/desktop.py
+++ b/vispy/gloo/gl/desktop.py
@@ -1,129 +1,77 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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.
+""" GL ES 2.0 API implemented via desktop GL (i.e subset of normal OpenGL).
 """
 
-from __future__ import print_function, division, absolute_import
+import sys
+import ctypes.util
 
-from OpenGL import GL as _GL
-import OpenGL.GL.framebufferobjects as FBO
+from . import _copy_gl_functions
+from ._constants import *  # noqa
 
-from . import _GL_ENUM
-from . import _desktop, _desktop_ext
+## Ctypes stuff
 
-# Prepare namespace with constants and ext
-from ._constants import *
-ext = _desktop_ext
 
+# Load the OpenGL library. We more or less follow the same approach
+# as PyOpenGL does internally
 
-
-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
+if sys.platform.startswith('win'):
+    # Windows
+    _lib = ctypes.windll.opengl32
     try:
-        func = getattr(_GL, funcname)
+        wglGetProcAddress = _lib.wglGetProcAddress
+        wglGetProcAddress.restype = ctypes.CFUNCTYPE(
+            ctypes.POINTER(ctypes.c_int))
+        wglGetProcAddress.argtypes = [ctypes.c_char_p]
+        _have_get_proc_address = True
     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
+        _have_get_proc_address = False
+else:
+    # Unix-ish
+    _have_get_proc_address = False
+    # Get filename
+    if sys.platform.startswith('darwin'):
+        _fname = ctypes.util.find_library('OpenGL')
+    else:
+        _fname = ctypes.util.find_library('GL')
+    if not _fname:
+        raise RuntimeError('Could not load OpenGL library.')
+    # Load lib
+    _lib = ctypes.cdll.LoadLibrary(_fname)
+
+
+def _have_context():
+    return _lib.glGetError() != 1282  # GL_INVALID_OPERATION
+
+
+def _get_gl_func(name, restype, argtypes):
+    # Based on a function in Pyglet
+    try:
+        # Try using normal ctypes stuff
+        func = getattr(_lib, name)
+        func.restype = restype
+        func.argtypes = argtypes
+        return func
+    except AttributeError:
+        if sys.platform.startswith('win'):
+            # Ask for a pointer to the function, this is the approach
+            # for OpenGL extensions on Windows
+            fargs = (restype,) + argtypes
+            ftype = ctypes.WINFUNCTYPE(*fargs)
+            if not _have_get_proc_address:
+                raise RuntimeError('Function %s not available.' % name)
+            if not _have_context():
+                raise RuntimeError('Using %s with no OpenGL context.' % name)
+            address = wglGetProcAddress(name.encode('utf-8'))
+            if address:
+                return ctypes.cast(address, ftype)
+        # If not Windows or if we did not return function object on Windows:
+        raise RuntimeError('Function %s not present in context.' % name)
+
+
+## Compatibility
 
 
 def glShaderSource_compat(handle, code):
@@ -134,32 +82,40 @@ def glShaderSource_compat(handle, code):
       * 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.')
+            logger.warning('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) )
+    if is_fragment and 'gl_PointCoord' in code:
+        enums.add(Enum('GL_VERTEX_PROGRAM_POINT_SIZE', 34370))
+        enums.add(Enum('GL_POINT_SPRITE', 34913))
     return enums
+    return []
+
+
+## Inject
+
+
+from . import _desktop
+_copy_gl_functions(_desktop, globals())
diff --git a/vispy/gloo/gl/ext.py b/vispy/gloo/gl/ext.py
deleted file mode 100644
index 261d7d9..0000000
--- a/vispy/gloo/gl/ext.py
+++ /dev/null
@@ -1,9 +0,0 @@
-""" 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/gl/pyopengl.py b/vispy/gloo/gl/pyopengl.py
new file mode 100644
index 0000000..5b22840
--- /dev/null
+++ b/vispy/gloo/gl/pyopengl.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" GL ES 2.0 API implemented via pyOpenGL library. Intended as a
+fallback and for testing.
+"""
+
+from OpenGL import GL as _GL
+import OpenGL.GL.framebufferobjects as _FBO
+
+from ...util import logger
+
+from . import _copy_gl_functions
+from ._constants import *  # noqa
+
+
+## Compatibility
+
+
+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
+            logger.warning('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(Enum('GL_VERTEX_PROGRAM_POINT_SIZE', 34370))
+        enums.add(Enum('GL_POINT_SPRITE', 34913))
+    return enums
+    return []
+
+
+def _patch():
+    """ Monkey-patch pyopengl to fix a bug in glBufferSubData. """
+    import sys
+    from OpenGL import GL
+    if sys.version_info > (3,):
+        buffersubdatafunc = GL.glBufferSubData
+        if hasattr(buffersubdatafunc, 'wrapperFunction'):
+            buffersubdatafunc = buffersubdatafunc.wrapperFunction
+        _m = sys.modules[buffersubdatafunc.__module__]
+        _m.long = int
+    
+    # Fix missing enum
+    try:
+        from OpenGL.GL.VERSION import GL_2_0
+        GL_2_0.GL_OBJECT_SHADER_SOURCE_LENGTH = GL_2_0.GL_SHADER_SOURCE_LENGTH
+    except Exception:
+        pass
+
+
+# Patch OpenGL package
+_patch()
+
+
+## Inject
+
+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 shows 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:
+            func = None
+    
+    # Try using "alias"
+    if not bool(func):
+        # Some functions are known by a slightly different name
+        # e.g. glDepthRangef, glClearDepthf
+        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)
+        logger.warning('warning: %s not available' % funcname)
+    return func
+
+
+def _inject():
+    """ Copy functions from OpenGL.GL into _pyopengl namespace.
+    """
+    NS = _pyopengl.__dict__
+    for glname, ourname in _pyopengl._functions_to_import:
+        func = _get_function_from_pyopengl(glname)
+        NS[ourname] = func
+
+
+from . import _pyopengl
+
+# Inject remaining functions from OpenGL.GL
+# copies name to _pyopengl namespace
+_inject()
+
+# Inject all function definitions in _pyopengl
+_copy_gl_functions(_pyopengl, globals())
diff --git a/vispy/shaders/__init__.py b/vispy/gloo/gl/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/gloo/gl/tests/__init__.py
diff --git a/vispy/gloo/gl/tests/test_basics.py b/vispy/gloo/gl/tests/test_basics.py
new file mode 100644
index 0000000..7d842e3
--- /dev/null
+++ b/vispy/gloo/gl/tests/test_basics.py
@@ -0,0 +1,276 @@
+""" Test to verify some basic functionality of our OpenGL API. We cover
+much more in the test_functionality. Together, these two tests should
+touch *all* ES 2.0 API calls.
+
+The only exception is glCompressedTexImage2D and glCompressedTexSubImage2D.
+"""
+
+import sys
+
+from nose.tools import assert_equal, assert_true  # noqa
+
+from vispy.app import Canvas
+from numpy.testing import assert_almost_equal
+from vispy.testing import (requires_application, requires_pyopengl, SkipTest,
+                           glut_skip)
+from vispy.ext.six import string_types
+from vispy.util import use_log_level
+from vispy.gloo import gl
+
+
+def teardown_module():
+    gl.use_gl()  # Reset to default
+
+
+ at requires_application()
+def test_basics_desktop():
+    """ Test desktop GL backend for basic functionality. """
+    glut_skip()
+    _test_basics('desktop')
+
+
+ at requires_application()
+def test_functionality_proxy():
+    """ Test GL proxy class for basic functionality. """
+    # By using debug mode, we are using the proxy class
+    glut_skip()
+    _test_basics('desktop debug')
+
+
+ at requires_application()
+ at requires_pyopengl()
+def test_basics_pypengl():
+    """ Test pyopengl GL backend for basic functionality. """
+    glut_skip()
+    _test_basics('pyopengl')
+
+
+ at requires_application()
+def test_functionality_angle():
+    """ Test angle GL backend for basic functionality. """
+    if True:
+        raise SkipTest('Skip Angle functionality test for now.')
+    if sys.platform.startswith('win'):
+        raise SkipTest('Can only test angle functionality on Windows.')
+
+    glut_skip()
+    _test_basics('angle')
+
+
+def _test_basics(backend):
+    """ Create app and canvas so we have a context. Then run tests.
+    """
+
+    # use the backend
+    with use_log_level('error', print_msg=False):
+        gl.use_gl(backend)  # pyopengl throws warning on injection
+
+    with Canvas():
+        _test_setting_parameters()
+        _test_enabling_disabling()
+        _test_setting_stuff()
+        _test_object_creation_and_deletion()
+        _test_fbo()
+        gl.glFinish()
+
+
+def _test_setting_parameters():
+    # Set some parameters and get result
+    clr = 1.0, 0.1, 0.2, 0.7
+    gl.glClearColor(*clr)
+    assert_almost_equal(gl.glGetParameter(gl.GL_COLOR_CLEAR_VALUE), clr)
+    #
+    gl.glCullFace(gl.GL_FRONT)
+    assert_equal(gl.glGetParameter(gl.GL_CULL_FACE_MODE), gl.GL_FRONT)
+    gl.glCullFace(gl.GL_BACK)
+    assert_equal(gl.glGetParameter(gl.GL_CULL_FACE_MODE), gl.GL_BACK)
+    #
+    gl.glDepthFunc(gl.GL_NOTEQUAL)
+    assert_equal(gl.glGetParameter(gl.GL_DEPTH_FUNC), gl.GL_NOTEQUAL)
+    #
+    val = 0.2, 0.3
+    gl.glDepthRange(*val)
+    assert_almost_equal(gl.glGetParameter(gl.GL_DEPTH_RANGE), val)
+    
+    gl.check_error()
+
+
+def _test_enabling_disabling():
+    # Enabling/disabling
+    gl.glEnable(gl.GL_DEPTH_TEST)
+    assert_equal(gl.glIsEnabled(gl.GL_DEPTH_TEST), True)
+    assert_equal(gl.glGetParameter(gl.GL_DEPTH_TEST), 1)
+    gl.glDisable(gl.GL_DEPTH_TEST)
+    assert_equal(gl.glIsEnabled(gl.GL_DEPTH_TEST), False)
+    assert_equal(gl.glGetParameter(gl.GL_DEPTH_TEST), 0)
+    #
+    gl.glEnable(gl.GL_BLEND)
+    assert_equal(gl.glIsEnabled(gl.GL_BLEND), True)
+    assert_equal(gl.glGetParameter(gl.GL_BLEND), 1)
+    gl.glDisable(gl.GL_BLEND)
+    assert_equal(gl.glIsEnabled(gl.GL_BLEND), False)
+    assert_equal(gl.glGetParameter(gl.GL_BLEND), 0)
+    
+    gl.check_error()
+
+
+def _test_setting_stuff():
+    # Set stuff to touch functions
+    
+    gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+    #
+    gl.glBlendColor(1.0, 1.0, 1.0, 1.0)
+    gl.glBlendEquation(gl.GL_FUNC_ADD)
+    gl.glBlendEquationSeparate(gl.GL_FUNC_ADD, gl.GL_FUNC_ADD)
+    gl.glBlendFunc(gl.GL_ONE, gl.GL_ZERO)
+    gl.glBlendFuncSeparate(gl.GL_ONE, gl.GL_ZERO, gl.GL_ONE, gl.GL_ZERO)
+    #
+    gl.glClearColor(0.0, 0.0, 0.0, 1.0)
+    gl.glClearDepth(1)
+    gl.glClearStencil(0)
+    #
+    gl.glColorMask(True, True, True, True)
+    gl.glDepthMask(False)
+    gl.glStencilMask(255)
+    gl.glStencilMaskSeparate(gl.GL_FRONT, 128)
+    #
+    gl.glStencilFunc(gl.GL_ALWAYS, 0, 255)
+    gl.glStencilFuncSeparate(gl.GL_FRONT, gl.GL_ALWAYS, 0, 255)
+    gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_KEEP)
+    gl.glStencilOpSeparate(gl.GL_FRONT, gl.GL_KEEP, gl.GL_KEEP, gl.GL_KEEP)
+    #
+    gl.glFrontFace(gl.GL_CW)
+    gl.glHint(gl.GL_GENERATE_MIPMAP_HINT, gl.GL_FASTEST)
+    gl.glLineWidth(2.0)
+    gl.glPolygonOffset(0.0, 0.0)
+    gl.glSampleCoverage(1.0, False)
+    
+    # And getting stuff
+    try:
+        with use_log_level('error', print_msg=False):
+            r, p = gl.glGetShaderPrecisionFormat(gl.GL_FRAGMENT_SHADER,
+                                                 gl.GL_HIGH_FLOAT)
+            gl.check_error()  # Sometimes the func is there but OpenGL errs
+    except Exception:
+        pass  # accept if the function is not there ...
+        # We should catch RuntimeError and GL.error.NullFunctionError,
+        # but PyOpenGL may not be available.
+        # On Travis this function was not there on one machine according
+        # to PyOpenGL, but our desktop backend worked fine ...
+        
+    #
+    v = gl.glGetParameter(gl.GL_VERSION)
+    assert_true(isinstance(v, string_types))
+    assert_true(len(v) > 0)
+    
+    gl.check_error()
+
+
+def _test_object_creation_and_deletion():
+
+    # Stuff that is originally glGenX
+    
+    # Note that if we test glIsTexture(x), we cannot assume x to be a
+    # nonexisting texture; we might have created a texture in another
+    # test and failed to clean it up.
+    
+    # Create/delete texture
+    #assert_equal(gl.glIsTexture(12), False)
+    handle = gl.glCreateTexture()
+    gl.glBindTexture(gl.GL_TEXTURE_2D, handle)
+    assert_equal(gl.glIsTexture(handle), True)
+    gl.glDeleteTexture(handle)
+    assert_equal(gl.glIsTexture(handle), False)
+
+    # Create/delete buffer
+    #assert_equal(gl.glIsBuffer(12), False)
+    handle = gl.glCreateBuffer()
+    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, handle)
+    assert_equal(gl.glIsBuffer(handle), True)
+    gl.glDeleteBuffer(handle)
+    assert_equal(gl.glIsBuffer(handle), False)
+
+    # Create/delete framebuffer
+    #assert_equal(gl.glIsFramebuffer(12), False)
+    handle = gl.glCreateFramebuffer()
+    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, handle)
+    assert_equal(gl.glIsFramebuffer(handle), True)
+    gl.glDeleteFramebuffer(handle)
+    assert_equal(gl.glIsFramebuffer(handle), False)
+
+    # Create/delete renderbuffer
+    #assert_equal(gl.glIsRenderbuffer(12), False)
+    handle = gl.glCreateRenderbuffer()
+    gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, handle)
+    assert_equal(gl.glIsRenderbuffer(handle), True)
+    gl.glDeleteRenderbuffer(handle)
+    assert_equal(gl.glIsRenderbuffer(handle), False)
+
+    # Stuff that is originally called glCreate
+
+    # Create/delete program
+    #assert_equal(gl.glIsProgram(12), False)
+    handle = gl.glCreateProgram()
+    assert_equal(gl.glIsProgram(handle), True)
+    gl.glDeleteProgram(handle)
+    assert_equal(gl.glIsProgram(handle), False)
+
+    # Create/delete shader
+    #assert_equal(gl.glIsShader(12), False)
+    handle = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+    assert_equal(gl.glIsShader(handle), True)
+    gl.glDeleteShader(handle)
+    assert_equal(gl.glIsShader(handle), False)
+    
+    gl.check_error()
+
+
+def _test_fbo():
+    
+    w, h = 120, 130
+    
+    # Create frame buffer
+    hframebuf = gl.glCreateFramebuffer()
+    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, hframebuf)
+    
+    #Create render buffer (for depth)
+    hrenderbuf = gl.glCreateRenderbuffer()
+    gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, hrenderbuf)
+    gl.glRenderbufferStorage(gl.GL_RENDERBUFFER, gl.GL_DEPTH_COMPONENT16, w, h)
+    gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT,
+                                 gl.GL_RENDERBUFFER, hrenderbuf)
+    
+    # Create texture (for color)
+    htex = gl.glCreateTexture()
+    gl.glBindTexture(gl.GL_TEXTURE_2D, htex)
+    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, gl.GL_RGB, 
+                    gl.GL_UNSIGNED_BYTE, (h, w))
+    gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
+                              gl.GL_TEXTURE_2D, htex, 0)
+    
+    # Check framebuffer status
+    status = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
+    assert_equal(status, gl.GL_FRAMEBUFFER_COMPLETE)
+    
+    # Tests renderbuffer params
+    name = gl.glGetFramebufferAttachmentParameter(
+        gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT, 
+        gl.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)
+    assert_equal(name, hrenderbuf)
+    #
+    width = gl.glGetRenderbufferParameter(gl.GL_RENDERBUFFER,
+                                          gl.GL_RENDERBUFFER_WIDTH)
+    assert_equal(width, w)
+    
+    # Touch copy tex functions
+    gl.glCopyTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, 0, 0, 30, 30,  0)
+    gl.glCopyTexSubImage2D(gl.GL_TEXTURE_2D, 0,  20, 20,  0, 0, 10, 10)
+    
+    gl.check_error()
+    
+    # Clean up
+    gl.glDeleteTexture(htex)
+    gl.glDeleteRenderbuffer(hrenderbuf)
+    gl.glDeleteFramebuffer(hframebuf)
+    
+    gl.check_error()
diff --git a/vispy/gloo/gl/tests/test_functionality.py b/vispy/gloo/gl/tests/test_functionality.py
new file mode 100644
index 0000000..156c656
--- /dev/null
+++ b/vispy/gloo/gl/tests/test_functionality.py
@@ -0,0 +1,557 @@
+""" Test to verify the functionality of the OpenGL backends. This test
+sets up a real visualization with shaders and all. This tests setting
+source code, setting texture and buffer data, and we touch many other
+functions of the API too. The end result is an image with four colored
+quads. The result is tested for pixel color.
+
+The visualization
+-----------------
+
+We create a visualization where the screen is divided in 4 quadrants,
+and each quadrant is drawn a different color (black, red, green,
+blue). The drawing is done for 50% using attribute data, and 50%
+using a texture. The end result should be fully saturated colors.
+
+Remember: the bottom left is (-1, -1) and the first quadrant. 
+
+"""
+import sys
+
+import numpy as np
+
+from nose.tools import assert_equal, assert_true
+from vispy.app import Canvas
+from numpy.testing import assert_almost_equal  # noqa
+from vispy.testing import (requires_application, requires_pyopengl, SkipTest,
+                           glut_skip)
+
+from vispy.gloo import gl
+
+# All these tests require a working backend.
+
+
+## High level tests
+
+def teardown_module():
+    gl.use_gl()  # Reset to default
+
+
+ at requires_application()
+def test_functionality_desktop():
+    """ Test desktop GL backend for full functionality. """
+    glut_skip()
+    _test_functonality('desktop')
+
+
+ at requires_application()
+def test_functionality_proxy():
+    """ Test GL proxy class for full functionality. """
+    # By using debug mode, we are using the proxy class
+    glut_skip()
+    _test_functonality('desktop debug')
+
+
+ at requires_application()
+ at requires_pyopengl()
+def test_functionality_pyopengl():
+    """ Test pyopengl GL backend for full functionality. """
+    glut_skip()
+    _test_functonality('pyopengl')
+
+
+ at requires_application()
+def test_functionality_angle():
+    """ Test angle GL backend for full functionality. """
+    if True:
+        raise SkipTest('Skip Angle functionality test for now.')
+    if sys.platform.startswith('win'):
+        raise SkipTest('Can only test angle functionality on Windows.')
+    glut_skip()
+    _test_functonality('angle')
+
+
+def _clear_screen():
+    gl.glClear(gl.GL_COLOR_BUFFER_BIT)
+    gl.glFinish()
+
+
+def _test_functonality(backend):
+    """ Create app and canvas so we have a context. Then run tests.
+    """
+    # use the backend
+    gl.use_gl(backend)
+    
+    with Canvas() as canvas:
+        _clear_screen()
+        
+        # Prepare
+        w, h = canvas.size
+        gl.glViewport(0, 0, w, h)
+        gl.glScissor(0, 0, w, h)  # touch
+        gl.glClearColor(0.0, 0.0, 0.0, 1.0)
+        
+        # Setup visualization, ensure to do it in a draw event
+        objects = _prepare_vis()
+        _clear_screen()
+        _draw1()
+        _clear_screen()
+        _draw2()
+        _clear_screen()
+        _draw3()
+
+        # Clean up
+        for delete_func, handle in objects:
+            delete_func(handle)
+        gl.glFinish()
+
+
+## Create CPU data
+
+# Create vertex and fragments shader. They are designed to that all
+# OpenGL func can be tested, i.e. all types of uniforms are present.
+# Most variables are nullified however, but we must make sure we do this
+# in a way that the compiler won't optimize out :)
+VERT = """
+attribute float a_1;
+attribute vec2 a_2;
+attribute vec3 a_3;
+attribute vec4 a_4;
+
+uniform float u_f1;
+uniform vec2 u_f2;
+uniform vec3 u_f3;
+uniform vec4 u_f4;
+
+uniform int u_i1;
+uniform ivec2 u_i2;
+uniform ivec3 u_i3;
+uniform ivec4 u_i4;
+
+uniform mat2 u_m2;
+uniform mat3 u_m3;
+uniform mat4 u_m4;
+
+varying vec2 v_2;  // tex coords
+varying vec4 v_4;  // attr colors
+
+void main()
+{   
+    float zero = float(u_i1);
+    
+    // Combine int with float uniforms (i.e. ints are "used")
+    float u1 = u_f1 + float(u_i1);
+    vec2 u2 = u_f2 + vec2(u_i2);
+    vec3 u3 = u_f3 + vec3(u_i3);
+    vec4 u4 = u_f4 + vec4(u_i4);
+    
+    // Set varyings (use every 2D and 4D variable, and u1)
+    v_2 = a_1 * a_2 + zero*u_m2 * a_2 * u2 * u1;
+    v_4 = u_m4 * a_4 * u4;
+    
+    // Set position (use 3D variables)
+    gl_Position = vec4(u_m3* a_3* u3, 1.0);
+}
+"""
+
+FRAG = """
+
+uniform sampler2D s_1;
+uniform int u_i1;
+varying vec2 v_2;  // rex coords
+varying vec4 v_4;  // attr colors
+
+void main()
+{   
+    float zero = float(u_i1);
+    gl_FragColor = (texture2D(s_1, v_2) + v_4);
+}
+"""
+
+# Color texture
+texquad = 5
+im1 = np.zeros((texquad*2, texquad*2, 3), np.uint8)
+im1[texquad:, :texquad, 0] = 128
+im1[texquad:, texquad:, 1] = 128
+im1[:texquad, texquad:, 2] = 128
+
+# Grayscale texture (uploaded but not used)
+im2 = im1[:, :, 0]  # A non-contiguous view
+assert im2.flags['C_CONTIGUOUS'] is False
+
+# Vertex Buffers
+
+# Create coordinates for upper left quad
+quad = np.array([[0, 0, 0], [-1, 0, 0], [-1, -1, 0], 
+                 [0, 0, 0], [-1, -1, 0], [0, -1, 0]], np.float32)
+N = quad.shape[0] * 4
+
+# buf3 contains coordinates in device coordinates for four quadrants
+buf3 = np.row_stack([quad + (0, 0, 0), quad + (0, 1, 0),
+                     quad + (1, 1, 0), quad + (1, 0, 0)]).astype(np.float32)
+
+# buf2 is texture coords. Note that this is a view on buf2
+buf2 = ((buf3+1.0)*0.5)[:, :2]   # not C-contiguous
+assert buf2.flags['C_CONTIGUOUS'] is False
+
+# Array of colors
+buf4 = np.zeros((N, 5), np.float32)
+buf4[6:12, 0] = 0.5
+buf4[12:18, 1] = 0.5
+buf4[18:24, 2] = 0.5
+buf4[:, 3] = 1.0  # alpha
+buf4 = buf4[:, :4]  # make non-contiguous
+
+# Element buffer
+# elements = np.arange(N, dtype=np.uint8)  # C-contiguous
+elements = np.arange(0, N, 0.5).astype(np.uint8)[::2]  # not C-contiguous
+helements = None  # the OpenGL object ref
+
+
+## The GL calls
+
+
+def _prepare_vis():
+    
+    objects = []
+    
+    # --- program and shaders
+    
+    # Create program and shaders
+    hprog = gl.glCreateProgram()
+    hvert = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+    hfrag = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+    objects.append((gl.glDeleteProgram, hprog))
+    objects.append((gl.glDeleteShader, hvert))
+    objects.append((gl.glDeleteShader, hfrag))
+    
+    # Compile source code
+    gl.glShaderSource_compat(hvert, VERT)
+    gl.glShaderSource_compat(hfrag, FRAG)
+    gl.glCompileShader(hvert)
+    gl.glCompileShader(hfrag)
+    
+    # Check
+    assert_equal(gl.glGetShaderInfoLog(hvert), '')
+    assert_equal(gl.glGetShaderInfoLog(hfrag), '')
+    assert_equal(gl.glGetShaderParameter(hvert, gl.GL_COMPILE_STATUS), 1)
+    assert_equal(gl.glGetShaderParameter(hfrag, gl.GL_COMPILE_STATUS), 1)
+    
+    # Attach and link
+    gl.glAttachShader(hprog, hvert)
+    gl.glAttachShader(hprog, hfrag)
+    # touch glDetachShader
+    gl.glDetachShader(hprog, hvert)
+    gl.glAttachShader(hprog, hvert)
+    gl.glLinkProgram(hprog)
+    
+    # Test that indeed these shaders are attached
+    attached_shaders = gl.glGetAttachedShaders(hprog)
+    assert_equal(set(attached_shaders), set([hvert, hfrag]))
+    
+    # Check
+    assert_equal(gl.glGetProgramInfoLog(hprog), '')
+    assert_equal(gl.glGetProgramParameter(hprog, gl.GL_LINK_STATUS), 1)
+    gl.glValidateProgram(hprog)
+    assert_equal(gl.glGetProgramParameter(hprog, gl.GL_VALIDATE_STATUS), 1)
+    
+    # Use it!
+    gl.glUseProgram(hprog)
+    
+    # Bind one attribute
+    gl.glBindAttribLocation(hprog, 1, 'a_2')
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # Check source
+    vert_source = gl.glGetShaderSource(hvert)
+    assert_true('attribute vec2 a_2;' in vert_source)
+    
+    # --- get information on attributes and uniforms
+    
+    # Count attribbutes and uniforms
+    natt = gl.glGetProgramParameter(hprog, gl.GL_ACTIVE_ATTRIBUTES)
+    nuni = gl.glGetProgramParameter(hprog, gl.GL_ACTIVE_UNIFORMS)
+    assert_equal(natt, 4)
+    assert_equal(nuni, 4+4+3+1)
+    
+    # Get names
+    names = {}
+    for i in range(natt):
+        name, count, type = gl.glGetActiveAttrib(hprog, i)
+        names[name] = type
+        assert_equal(count, 1)
+    for i in range(nuni):
+        name, count, type = gl.glGetActiveUniform(hprog, i)
+        names[name] = type
+        assert_equal(count, 1)
+    
+    # Check
+    assert_equal(names['a_1'], gl.GL_FLOAT)
+    assert_equal(names['a_2'], gl.GL_FLOAT_VEC2)
+    assert_equal(names['a_3'], gl.GL_FLOAT_VEC3)
+    assert_equal(names['a_4'], gl.GL_FLOAT_VEC4)
+    assert_equal(names['s_1'], gl.GL_SAMPLER_2D)
+    #
+    for i, type in enumerate([gl.GL_FLOAT, gl.GL_FLOAT_VEC2, 
+                              gl.GL_FLOAT_VEC3, gl.GL_FLOAT_VEC4]):
+        assert_equal(names['u_f%i' % (i+1)], type)
+    for i, type in enumerate([gl.GL_INT, gl.GL_INT_VEC2, 
+                              gl.GL_INT_VEC3, gl.GL_INT_VEC4]):
+        assert_equal(names['u_i%i' % (i+1)], type)
+    for i, type in enumerate([gl.GL_FLOAT_MAT2, gl.GL_FLOAT_MAT3, 
+                              gl.GL_FLOAT_MAT4]):
+        assert_equal(names['u_m%i' % (i+2)], type)
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- texture
+    
+    # Create, bind, activate
+    htex = gl.glCreateTexture()
+    objects.append((gl.glDeleteTexture, htex))
+    gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
+    gl.glBindTexture(gl.GL_TEXTURE_2D, htex)
+    
+    # Allocate data and upload
+    # This data is luminance and not C-contiguous
+    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_LUMINANCE, gl.GL_LUMINANCE, 
+                    gl.GL_UNSIGNED_BYTE, im2)  # touch
+    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_LUMINANCE, gl.GL_LUMINANCE, 
+                    gl.GL_UNSIGNED_BYTE, im2.shape[:2])
+    gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, gl.GL_LUMINANCE,
+                       gl.GL_UNSIGNED_BYTE, im2)
+    
+    # Set texture parameters (use f and i to touch both)
+    T = gl.GL_TEXTURE_2D
+    gl.glTexParameterf(T, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
+    gl.glTexParameteri(T, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
+    
+    # Re-allocate data and upload 
+    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, gl.GL_RGB, 
+                    gl.GL_UNSIGNED_BYTE, im1.shape[:2])
+    gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, gl.GL_RGB,
+                       gl.GL_UNSIGNED_BYTE, im1)
+    
+    # Attach!
+    loc = gl.glGetUniformLocation(hprog, 's_1')
+    unit = 0
+    gl.glActiveTexture(gl.GL_TEXTURE0+unit)
+    gl.glUniform1i(loc, unit) 
+    
+    # Mipmaps (just to touch this function)
+    gl.glGenerateMipmap(gl.GL_TEXTURE_2D)
+    
+    # Check min filter (touch getTextParameter)
+    minfilt = gl.glGetTexParameter(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER)
+    assert_equal(minfilt, gl.GL_LINEAR)
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- buffer vec2 (contiguous VBO)
+    
+    # Create buffer
+    hbuf2 = gl.glCreateBuffer()
+    objects.append((gl.glDeleteBuffer, hbuf2))
+    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, hbuf2)
+
+    # Allocate and set data
+    gl.glBufferData(gl.GL_ARRAY_BUFFER, buf2.nbytes, gl.GL_DYNAMIC_DRAW)
+    gl.glBufferSubData(gl.GL_ARRAY_BUFFER, 0, buf2)
+    
+    # Attach!
+    loc = gl.glGetAttribLocation(hprog, 'a_2')
+    gl.glDisableVertexAttribArray(loc)  # touch
+    gl.glEnableVertexAttribArray(loc)
+    gl.glVertexAttribPointer(loc, 2, gl.GL_FLOAT, False, 2*4, 0)
+    
+    # Check (touch glGetBufferParameter, glGetVertexAttrib and
+    # glGetVertexAttribOffset)
+    size = gl.glGetBufferParameter(gl.GL_ARRAY_BUFFER, gl.GL_BUFFER_SIZE)
+    assert_equal(size, buf2.nbytes)
+    stride = gl.glGetVertexAttrib(loc, gl.GL_VERTEX_ATTRIB_ARRAY_STRIDE)
+    assert_equal(stride, 2*4)
+    offset = gl.glGetVertexAttribOffset(loc, gl.GL_VERTEX_ATTRIB_ARRAY_POINTER)
+    assert_equal(offset, 0)
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- buffer vec3 (non-contiguous VBO)
+    
+    # Create buffer
+    hbuf3 = gl.glCreateBuffer()
+    objects.append((gl.glDeleteBuffer, hbuf3))
+    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, hbuf3)
+
+    # Allocate and set data
+    gl.glBufferData(gl.GL_ARRAY_BUFFER, buf3.nbytes, gl.GL_DYNAMIC_DRAW)
+    gl.glBufferSubData(gl.GL_ARRAY_BUFFER, 0, buf3)
+    
+    # Attach!
+    loc = gl.glGetAttribLocation(hprog, 'a_3')
+    gl.glEnableVertexAttribArray(loc)
+    gl.glVertexAttribPointer(loc, 3, gl.GL_FLOAT, False, 3*4, 0)
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- buffer vec4 (client vertex data)
+    
+    # Select no FBO
+    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
+    
+    # Attach!
+    loc = gl.glGetAttribLocation(hprog, 'a_4')
+    gl.glEnableVertexAttribArray(loc)
+    gl.glVertexAttribPointer(loc, 4, gl.GL_FLOAT, False, 4*4, buf4)
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- element buffer
+    
+    # Create buffer
+    global helements
+    helements = gl.glCreateBuffer()
+    objects.append((gl.glDeleteBuffer, helements))
+    gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, helements)
+
+    # Allocate and set data
+    gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, elements, gl.GL_DYNAMIC_DRAW)
+    gl.glBufferSubData(gl.GL_ELEMENT_ARRAY_BUFFER, 0, elements)
+    
+    # Turn off
+    gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, 0)
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- uniforms
+    
+    # Set integer uniforms to 0
+    # We set them twice just to touch both i and iv functions
+    for i, fun1, fun2 in [(1, gl.glUniform1i, gl.glUniform1iv),
+                          (2, gl.glUniform2i, gl.glUniform2iv),
+                          (3, gl.glUniform3i, gl.glUniform3iv),
+                          (4, gl.glUniform4i, gl.glUniform4iv)]:
+        name = 'u_i%i' % i
+        value = [0] * i
+        loc = gl.glGetUniformLocation(hprog, name)
+        fun1(loc, *value)  # e.g. glUniform4i
+        fun2(loc, 1, value)  # e.g. glUniform4iv
+
+    # Set float uniforms to 1.0
+    # We set them twice just to touch both i and iv functions
+    for i, fun1, fun2 in [(1, gl.glUniform1f, gl.glUniform1fv),
+                          (2, gl.glUniform2f, gl.glUniform2fv),
+                          (3, gl.glUniform3f, gl.glUniform3fv),
+                          (4, gl.glUniform4f, gl.glUniform4fv)]:
+        name = 'u_f%i' % i
+        value = [1.0] * i
+        loc = gl.glGetUniformLocation(hprog, name)
+        fun1(loc, *value)  # e.g. glUniform4f
+        fun2(loc, 1, value)  # e.g. glUniform4fv
+    
+    # Set matrix uniforms
+    m = np.eye(5, dtype='float32')
+    loc = gl.glGetUniformLocation(hprog, 'u_m2')
+    gl.glUniformMatrix2fv(loc, 1, False, m[:2, :2])
+    #
+    loc = gl.glGetUniformLocation(hprog, 'u_m3')
+    m = np.eye(3, dtype='float32')
+    gl.glUniformMatrix3fv(loc, 1, False, m[:3, :3])
+    #
+    loc = gl.glGetUniformLocation(hprog, 'u_m4')
+    m = np.eye(4, dtype='float32')
+    gl.glUniformMatrix4fv(loc, 1, False, m[:4, :4])
+    
+    # Check some uniforms
+    loc = gl.glGetUniformLocation(hprog, 'u_i1')
+    assert_equal(gl.glGetUniform(hprog, loc), 0)
+    loc = gl.glGetUniformLocation(hprog, 'u_i2')
+    assert_equal(gl.glGetUniform(hprog, loc), (0, 0))
+    loc = gl.glGetUniformLocation(hprog, 'u_f2')
+    assert_equal(gl.glGetUniform(hprog, loc), (1.0, 1.0))
+    
+    # Check if all is ok
+    assert_equal(gl.glGetError(), 0)
+    
+    # --- attributes 
+    
+    # Constant values for attributes. We do not even use this ...
+    loc = gl.glGetAttribLocation(hprog, 'a_1')
+    gl.glVertexAttrib1f(loc, 1.0)
+    loc = gl.glGetAttribLocation(hprog, 'a_2')
+    gl.glVertexAttrib2f(loc, 1.0, 1.0)
+    loc = gl.glGetAttribLocation(hprog, 'a_3')
+    gl.glVertexAttrib3f(loc, 1.0, 1.0, 1.0)
+    loc = gl.glGetAttribLocation(hprog, 'a_4')
+    gl.glVertexAttrib4f(loc, 1.0, 1.0, 1.0, 1.0)
+    
+    # --- flush and finish
+    
+    # Not really necessary, but we want to touch the functions
+    gl.glFlush()
+    gl.glFinish()
+    
+    #print([i[1] for i in objects])
+    return objects
+
+
+def _draw1():
+    # Draw using arrays
+    gl.glDrawArrays(gl.GL_TRIANGLES, 0, N)
+    gl.glFinish()
+    _check_result()
+
+
+def _draw2():
+    # Draw using elements via buffer
+    gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, helements)
+    gl.glDrawElements(gl.GL_TRIANGLES, elements.size, gl.GL_UNSIGNED_BYTE, 0)
+    gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, 0)
+    gl.glFinish()
+    _check_result()
+
+
+def _draw3():
+    # Draw using elements via numpy array
+    gl.glDrawElements(gl.GL_TRIANGLES, 
+                      elements.size, gl.GL_UNSIGNED_BYTE, elements)
+    gl.glFinish()
+    _check_result()
+
+
+def _check_result(assert_result=True):
+    """ Test the color of each quadrant by picking the center pixel 
+    of each quadrant and comparing it with the reference color.
+    """
+    
+    # Take screenshot
+    x, y, w, h = gl.glGetParameter(gl.GL_VIEWPORT)
+    data = gl.glReadPixels(x, y, w, h, gl.GL_RGB, gl.GL_UNSIGNED_BYTE)
+    im = np.frombuffer(data, np.uint8)
+    im.shape = h, w, 3
+    
+    # Get center pixel from each quadrant
+    pix1 = tuple(im[int(1*h/4), int(1*w/4)])
+    pix2 = tuple(im[int(3*h/4), int(1*w/4)])
+    pix3 = tuple(im[int(3*h/4), int(3*w/4)])
+    pix4 = tuple(im[int(1*h/4), int(3*w/4)])
+    #print(pix1, pix2, pix3, pix4)
+   
+    if assert_result:
+        # Test their value
+        assert_equal(pix1, (0, 0, 0))
+        assert_equal(pix2, (255, 0, 0))
+        assert_equal(pix3, (0, 255, 0))
+        assert_equal(pix4, (0, 0, 255))
+
+
+if __name__ == '__main__':
+    test_functionality_desktop()
+    test_functionality_pyopengl()
+    test_functionality_proxy()
diff --git a/vispy/gloo/gl/tests/test_names.py b/vispy/gloo/gl/tests/test_names.py
new file mode 100644
index 0000000..bf08d0b
--- /dev/null
+++ b/vispy/gloo/gl/tests/test_names.py
@@ -0,0 +1,232 @@
+""" Tests to verify that all ES 2.0 function names are defined in all
+backends, and no more than that.
+"""
+
+from nose.tools import assert_equal
+from vispy.testing import requires_pyopengl
+
+from vispy.gloo import gl
+
+
+class _DummyObject:
+    """ To be able to import angle even in Linux, so that we can test the
+    names defined inside.
+    """
+    def LoadLibrary(self, fname):
+        return _DummyObject()
+
+    def __getattr__(self, name):
+        setattr(self, name, self.__class__())
+        return getattr(self, name)
+
+
+def _test_function_names(mod):
+    fnames = set([name for name in dir(mod) if name.startswith('gl')])
+    assert_equal(function_names.difference(fnames), set())
+    assert_equal(fnames.difference(function_names), set())
+
+
+def _test_contant_names(mod):
+    cnames = set([name for name in dir(mod) if name.startswith('GL')])
+    assert_equal(constant_names.difference(cnames), set())
+    assert_equal(cnames.difference(constant_names), set())
+
+
+def test_destop():
+    """ Desktop backend should have all ES 2.0 names. No more, no less. """
+    from vispy.gloo.gl import desktop
+    _test_function_names(desktop)
+    _test_contant_names(desktop)
+
+
+def test_angle():
+    """ Angle backend should have all ES 2.0 names. No more, no less. """
+    # Import. Install a dummy lib so that at least we can import angle.
+    try:
+        from vispy.gloo.gl import angle  # noqa
+    except Exception:
+        import ctypes
+        ctypes.windll = _DummyObject()
+    from vispy.gloo.gl import angle  # noqa
+
+    # Test
+    _test_function_names(angle)
+    _test_contant_names(angle)
+
+
+ at requires_pyopengl()
+def test_pyopengl():
+    """ Pyopengl backend should have all ES 2.0 names. No more, no less. """
+    from vispy.gloo.gl import pyopengl
+    _test_function_names(pyopengl)
+    _test_contant_names(pyopengl)
+
+
+def test_proxy():
+    """ GLProxy class should have all ES 2.0 names. No more, no less. """
+    _test_function_names(gl.proxy)
+    _test_contant_names(gl._constants)
+
+
+def test_main():
+    """ Main gl namespace should have all ES 2.0 names. No more, no less. """
+    _test_function_names(gl)
+    _test_contant_names(gl)
+
+
+def test_webgl():
+    """ Webgl backend should have all ES 2.0 names. No more, no less. """
+    from vispy.gloo.gl import webgl
+    _test_function_names(webgl)
+    _test_contant_names(webgl)
+
+
+def _main():
+    """ For testing this test suite :)
+    """
+    test_main()
+    test_proxy()
+    test_destop()
+    test_angle()
+    test_pyopengl()
+    test_webgl()
+
+
+# Note: I took these names below from _main and _constants, which is a
+# possible cause for error.
+
+function_names = """
+glActiveTexture glAttachShader glBindAttribLocation glBindBuffer
+glBindFramebuffer glBindRenderbuffer glBindTexture glBlendColor
+glBlendEquation glBlendEquationSeparate glBlendFunc glBlendFuncSeparate
+glBufferData glBufferSubData glCheckFramebufferStatus glClear
+glClearColor glClearDepth glClearStencil glColorMask glCompileShader
+glCompressedTexImage2D glCompressedTexSubImage2D glCopyTexImage2D
+glCopyTexSubImage2D glCreateBuffer glCreateFramebuffer glCreateProgram
+glCreateRenderbuffer glCreateShader glCreateTexture glCullFace
+glDeleteBuffer glDeleteFramebuffer glDeleteProgram glDeleteRenderbuffer
+glDeleteShader glDeleteTexture glDepthFunc glDepthMask glDepthRange
+glDetachShader glDisable glDisableVertexAttribArray glDrawArrays
+glDrawElements glEnable glEnableVertexAttribArray glFinish glFlush
+glFramebufferRenderbuffer glFramebufferTexture2D glFrontFace
+glGenerateMipmap glGetActiveAttrib glGetActiveUniform
+glGetAttachedShaders glGetAttribLocation glGetBufferParameter glGetError
+glGetFramebufferAttachmentParameter glGetParameter glGetProgramInfoLog
+glGetProgramParameter glGetRenderbufferParameter glGetShaderInfoLog
+glGetShaderParameter glGetShaderPrecisionFormat glGetShaderSource
+glGetTexParameter glGetUniform glGetUniformLocation glGetVertexAttrib
+glGetVertexAttribOffset glHint glIsBuffer glIsEnabled glIsFramebuffer
+glIsProgram glIsRenderbuffer glIsShader glIsTexture glLineWidth
+glLinkProgram glPixelStorei glPolygonOffset glReadPixels
+glRenderbufferStorage glSampleCoverage glScissor glShaderSource
+glShaderSource_compat glStencilFunc glStencilFuncSeparate glStencilMask
+glStencilMaskSeparate glStencilOp glStencilOpSeparate glTexImage2D
+glTexParameterf glTexParameteri glTexSubImage2D glUniform1f glUniform1fv
+glUniform1i glUniform1iv glUniform2f glUniform2fv glUniform2i
+glUniform2iv glUniform3f glUniform3fv glUniform3i glUniform3iv
+glUniform4f glUniform4fv glUniform4i glUniform4iv glUniformMatrix2fv
+glUniformMatrix3fv glUniformMatrix4fv glUseProgram glValidateProgram
+glVertexAttrib1f glVertexAttrib2f glVertexAttrib3f glVertexAttrib4f
+glVertexAttribPointer glViewport
+""".replace('\n', ' ')
+
+constant_names = """
+GL_ACTIVE_ATTRIBUTES GL_ACTIVE_ATTRIBUTE_MAX_LENGTH GL_ACTIVE_TEXTURE
+GL_ACTIVE_UNIFORMS GL_ACTIVE_UNIFORM_MAX_LENGTH
+GL_ALIASED_LINE_WIDTH_RANGE GL_ALIASED_POINT_SIZE_RANGE GL_ALPHA
+GL_ALPHA_BITS GL_ALWAYS GL_ARRAY_BUFFER GL_ARRAY_BUFFER_BINDING
+GL_ATTACHED_SHADERS GL_BACK GL_BLEND GL_BLEND_COLOR GL_BLEND_DST_ALPHA
+GL_BLEND_DST_RGB GL_BLEND_EQUATION GL_BLEND_EQUATION_ALPHA
+GL_BLEND_EQUATION_RGB GL_BLEND_SRC_ALPHA GL_BLEND_SRC_RGB GL_BLUE_BITS
+GL_BOOL GL_BOOL_VEC2 GL_BOOL_VEC3 GL_BOOL_VEC4 GL_BUFFER_SIZE
+GL_BUFFER_USAGE GL_BYTE GL_CCW GL_CLAMP_TO_EDGE GL_COLOR_ATTACHMENT0
+GL_COLOR_BUFFER_BIT GL_COLOR_CLEAR_VALUE GL_COLOR_WRITEMASK
+GL_COMPILE_STATUS GL_COMPRESSED_TEXTURE_FORMATS GL_CONSTANT_ALPHA
+GL_CONSTANT_COLOR GL_CULL_FACE GL_CULL_FACE_MODE GL_CURRENT_PROGRAM
+GL_CURRENT_VERTEX_ATTRIB GL_CW GL_DECR GL_DECR_WRAP GL_DELETE_STATUS
+GL_DEPTH_ATTACHMENT GL_DEPTH_BITS GL_DEPTH_BUFFER_BIT
+GL_DEPTH_CLEAR_VALUE GL_DEPTH_COMPONENT GL_DEPTH_COMPONENT16
+GL_DEPTH_FUNC GL_DEPTH_RANGE GL_DEPTH_TEST GL_DEPTH_WRITEMASK GL_DITHER
+GL_DONT_CARE GL_DST_ALPHA GL_DST_COLOR GL_DYNAMIC_DRAW
+GL_ELEMENT_ARRAY_BUFFER GL_ELEMENT_ARRAY_BUFFER_BINDING GL_EQUAL
+GL_ES_VERSION_2_0 GL_EXTENSIONS GL_FALSE GL_FASTEST GL_FIXED GL_FLOAT
+GL_FLOAT_MAT2 GL_FLOAT_MAT3 GL_FLOAT_MAT4 GL_FLOAT_VEC2 GL_FLOAT_VEC3
+GL_FLOAT_VEC4 GL_FRAGMENT_SHADER GL_FRAMEBUFFER
+GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME
+GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE
+GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL GL_FRAMEBUFFER_BINDING
+GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
+GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
+GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT GL_FRAMEBUFFER_UNSUPPORTED
+GL_FRONT GL_FRONT_AND_BACK GL_FRONT_FACE GL_FUNC_ADD
+GL_FUNC_REVERSE_SUBTRACT GL_FUNC_SUBTRACT GL_GENERATE_MIPMAP_HINT
+GL_GEQUAL GL_GREATER GL_GREEN_BITS GL_HIGH_FLOAT GL_HIGH_INT
+GL_IMPLEMENTATION_COLOR_READ_FORMAT GL_IMPLEMENTATION_COLOR_READ_TYPE
+GL_INCR GL_INCR_WRAP GL_INFO_LOG_LENGTH GL_INT GL_INT_VEC2 GL_INT_VEC3
+GL_INT_VEC4 GL_INVALID_ENUM GL_INVALID_FRAMEBUFFER_OPERATION
+GL_INVALID_OPERATION GL_INVALID_VALUE GL_INVERT GL_KEEP GL_LEQUAL
+GL_LESS GL_LINEAR GL_LINEAR_MIPMAP_LINEAR GL_LINEAR_MIPMAP_NEAREST
+GL_LINES GL_LINE_LOOP GL_LINE_STRIP GL_LINE_WIDTH GL_LINK_STATUS
+GL_LOW_FLOAT GL_LOW_INT GL_LUMINANCE GL_LUMINANCE_ALPHA
+GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS GL_MAX_CUBE_MAP_TEXTURE_SIZE
+GL_MAX_FRAGMENT_UNIFORM_VECTORS GL_MAX_RENDERBUFFER_SIZE
+GL_MAX_TEXTURE_IMAGE_UNITS GL_MAX_TEXTURE_SIZE GL_MAX_VARYING_VECTORS
+GL_MAX_VERTEX_ATTRIBS GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS
+GL_MAX_VERTEX_UNIFORM_VECTORS GL_MAX_VIEWPORT_DIMS GL_MEDIUM_FLOAT
+GL_MEDIUM_INT GL_MIRRORED_REPEAT GL_NEAREST GL_NEAREST_MIPMAP_LINEAR
+GL_NEAREST_MIPMAP_NEAREST GL_NEVER GL_NICEST GL_NONE GL_NOTEQUAL
+GL_NO_ERROR GL_NUM_COMPRESSED_TEXTURE_FORMATS
+GL_NUM_SHADER_BINARY_FORMATS GL_ONE GL_ONE_MINUS_CONSTANT_ALPHA
+GL_ONE_MINUS_CONSTANT_COLOR GL_ONE_MINUS_DST_ALPHA
+GL_ONE_MINUS_DST_COLOR GL_ONE_MINUS_SRC_ALPHA GL_ONE_MINUS_SRC_COLOR
+GL_OUT_OF_MEMORY GL_PACK_ALIGNMENT GL_POINTS GL_POLYGON_OFFSET_FACTOR
+GL_POLYGON_OFFSET_FILL GL_POLYGON_OFFSET_UNITS GL_RED_BITS
+GL_RENDERBUFFER GL_RENDERBUFFER_ALPHA_SIZE GL_RENDERBUFFER_BINDING
+GL_RENDERBUFFER_BLUE_SIZE GL_RENDERBUFFER_DEPTH_SIZE
+GL_RENDERBUFFER_GREEN_SIZE GL_RENDERBUFFER_HEIGHT
+GL_RENDERBUFFER_INTERNAL_FORMAT GL_RENDERBUFFER_RED_SIZE
+GL_RENDERBUFFER_STENCIL_SIZE GL_RENDERBUFFER_WIDTH GL_RENDERER GL_REPEAT
+GL_REPLACE GL_RGB GL_RGB565 GL_RGB5_A1 GL_RGBA GL_RGBA4 GL_SAMPLER_2D
+GL_SAMPLER_CUBE GL_SAMPLES GL_SAMPLE_ALPHA_TO_COVERAGE GL_SAMPLE_BUFFERS
+GL_SAMPLE_COVERAGE GL_SAMPLE_COVERAGE_INVERT GL_SAMPLE_COVERAGE_VALUE
+GL_SCISSOR_BOX GL_SCISSOR_TEST GL_SHADER_BINARY_FORMATS
+GL_SHADER_COMPILER GL_SHADER_SOURCE_LENGTH GL_SHADER_TYPE
+GL_SHADING_LANGUAGE_VERSION GL_SHORT GL_SRC_ALPHA GL_SRC_ALPHA_SATURATE
+GL_SRC_COLOR GL_STATIC_DRAW GL_STENCIL_ATTACHMENT GL_STENCIL_BACK_FAIL
+GL_STENCIL_BACK_FUNC GL_STENCIL_BACK_PASS_DEPTH_FAIL
+GL_STENCIL_BACK_PASS_DEPTH_PASS GL_STENCIL_BACK_REF
+GL_STENCIL_BACK_VALUE_MASK GL_STENCIL_BACK_WRITEMASK GL_STENCIL_BITS
+GL_STENCIL_BUFFER_BIT GL_STENCIL_CLEAR_VALUE GL_STENCIL_FAIL
+GL_STENCIL_FUNC GL_STENCIL_INDEX8 GL_STENCIL_PASS_DEPTH_FAIL
+GL_STENCIL_PASS_DEPTH_PASS GL_STENCIL_REF GL_STENCIL_TEST
+GL_STENCIL_VALUE_MASK GL_STENCIL_WRITEMASK GL_STREAM_DRAW
+GL_SUBPIXEL_BITS GL_TEXTURE GL_TEXTURE0 GL_TEXTURE1 GL_TEXTURE10
+GL_TEXTURE11 GL_TEXTURE12 GL_TEXTURE13 GL_TEXTURE14 GL_TEXTURE15
+GL_TEXTURE16 GL_TEXTURE17 GL_TEXTURE18 GL_TEXTURE19 GL_TEXTURE2
+GL_TEXTURE20 GL_TEXTURE21 GL_TEXTURE22 GL_TEXTURE23 GL_TEXTURE24
+GL_TEXTURE25 GL_TEXTURE26 GL_TEXTURE27 GL_TEXTURE28 GL_TEXTURE29
+GL_TEXTURE3 GL_TEXTURE30 GL_TEXTURE31 GL_TEXTURE4 GL_TEXTURE5
+GL_TEXTURE6 GL_TEXTURE7 GL_TEXTURE8 GL_TEXTURE9 GL_TEXTURE_2D
+GL_TEXTURE_BINDING_2D GL_TEXTURE_BINDING_CUBE_MAP GL_TEXTURE_CUBE_MAP
+GL_TEXTURE_CUBE_MAP_NEGATIVE_X GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
+GL_TEXTURE_CUBE_MAP_NEGATIVE_Z GL_TEXTURE_CUBE_MAP_POSITIVE_X
+GL_TEXTURE_CUBE_MAP_POSITIVE_Y GL_TEXTURE_CUBE_MAP_POSITIVE_Z
+GL_TEXTURE_MAG_FILTER GL_TEXTURE_MIN_FILTER GL_TEXTURE_WRAP_S
+GL_TEXTURE_WRAP_T GL_TRIANGLES GL_TRIANGLE_FAN GL_TRIANGLE_STRIP GL_TRUE
+GL_UNPACK_ALIGNMENT GL_UNSIGNED_BYTE GL_UNSIGNED_INT GL_UNSIGNED_SHORT
+GL_UNSIGNED_SHORT_4_4_4_4 GL_UNSIGNED_SHORT_5_5_5_1
+GL_UNSIGNED_SHORT_5_6_5 GL_VALIDATE_STATUS GL_VENDOR GL_VERSION
+GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING GL_VERTEX_ATTRIB_ARRAY_ENABLED
+GL_VERTEX_ATTRIB_ARRAY_NORMALIZED GL_VERTEX_ATTRIB_ARRAY_POINTER
+GL_VERTEX_ATTRIB_ARRAY_SIZE GL_VERTEX_ATTRIB_ARRAY_STRIDE
+GL_VERTEX_ATTRIB_ARRAY_TYPE GL_VERTEX_SHADER GL_VIEWPORT GL_ZERO
+""".replace('\n', ' ')
+
+function_names = [n.strip() for n in function_names.split(' ')]
+function_names = set([n for n in function_names if n])
+constant_names = [n.strip() for n in constant_names.split(' ')]
+constant_names = set([n for n in constant_names if n])
+
+if __name__ == '__main__':
+    _main()
diff --git a/vispy/gloo/gl/tests/test_use.py b/vispy/gloo/gl/tests/test_use.py
new file mode 100644
index 0000000..8cd79f3
--- /dev/null
+++ b/vispy/gloo/gl/tests/test_use.py
@@ -0,0 +1,58 @@
+""" Test the use function.
+"""
+
+from vispy.testing import assert_is, requires_pyopengl
+
+from vispy.gloo import gl
+
+
+def teardown_module():
+    gl.use_gl()  # Reset to default
+
+
+ at requires_pyopengl()
+def test_use_desktop():
+    """ Testing that gl.use injects all names in gl namespace """
+
+    # Use desktop
+    gl.use_gl('desktop')
+    #
+    for name in dir(gl.desktop):
+        if name.lower().startswith('gl'):
+            val1 = getattr(gl, name)
+            val2 = getattr(gl.desktop, name)
+            assert_is(val1, val2)
+
+    # Use pyopengl
+    gl.use_gl('pyopengl')
+    #
+    for name in dir(gl.desktop):
+        if name.lower().startswith('gl'):
+            val1 = getattr(gl, name)
+            val2 = getattr(gl.pyopengl, name)
+            assert_is(val1, val2)
+    
+    # Use webgl
+    gl.use_gl('webgl')
+    #
+    for name in dir(gl.desktop):
+        if name.lower().startswith('gl'):
+            val1 = getattr(gl, name)
+            val2 = getattr(gl.webgl, name)
+            assert_is(val1, val2)
+    
+    # Touch debug wrapper stuff
+    gl.use_gl('desktop debug')
+    
+    # Use desktop again
+    gl.use_gl('desktop')
+    #
+    for name in dir(gl.desktop):
+        if name.lower().startswith('gl'):
+            val1 = getattr(gl, name)
+            val2 = getattr(gl.desktop, name)
+            assert_is(val1, val2)
+
+
+if __name__ == '__main__':
+    test_use_desktop()
diff --git a/vispy/gloo/gl/webgl.py b/vispy/gloo/gl/webgl.py
new file mode 100644
index 0000000..84e060e
--- /dev/null
+++ b/vispy/gloo/gl/webgl.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" GL ES 2.0 API implemented via WebGL.
+"""
+
+from . import BaseGLProxy, _copy_gl_functions
+from ._constants import *  # noqa
+
+
+class WebGLProxy(BaseGLProxy):
+    """ Dummy proxy class for WebGL. More or less psuedo code for now :)
+    But this should get whomever is going to work on WebGL a good place to
+    start.
+    Note that in order to use WebGL, we also need a WebGL app, probably
+    also via some sort of proxy class. 
+    """
+    
+    def __call__(self, funcname, returns, *args):
+        
+        self.websocket.send(funcname, *args)
+        if returns:
+            return self.websocket.wait_for_result()
+
+
+# Instantiate proxy and inject functions
+_proxy = WebGLProxy()
+_copy_gl_functions(_proxy, globals())
diff --git a/vispy/gloo/globject.py b/vispy/gloo/globject.py
index beb17e2..d0962a6 100644
--- a/vispy/gloo/globject.py
+++ b/vispy/gloo/globject.py
@@ -1,153 +1,89 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
 # 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.
+    """ Generic GL object that may live both on CPU and GPU 
     """
-    
-    # Internal id counter to keep track of created objects
+
+    # Internal id counter to keep track of GPU 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)
+        """ Initialize the object in the default state """
+
+        self._handle = -1
+        self._target = None
+        self._need_create = True
+        self._need_delete = False
+
         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))
-    
-    
+        # Worse, there might be multiple contexts and calling delete()
+        # at the wrong moment might remove other gl objects, leading to
+        # very strange and hard to debug behavior.
+        #
+        # So we don't do anything. If each GLObject was aware of the
+        # context in which it resides, we could do auto-cleanup though...
+        # todo: it's not very Pythonic to have to delete an object.
+        pass
+
     def delete(self):
-        """ Delete the object from OpenGl memory. """
+        """ Delete the object from GPU memory """
 
-        # Only delete object if it was created on GPU
-        if self._handle:
+        if self._need_delete:
             self._delete()
-        # Reset
-        self._handle = 0
-        self._valid = False
-    
-    
+        self._handle = -1
+        self._need_create = True
+        self._need_delete = 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
+        """ Activate the object on GPU """
+        # As a base class, we only provide functionality for
+        # automatically creating the object. The other stages are so
+        # different that it's more clear if each GLObject specifies
+        # what it does in _activate().
+        if self._need_create:
+            self._create()
+            self._need_create = False
         self._activate()
-    
-    
+
     def deactivate(self):
-        """ Deactivate the object.
-        """
+        """ Deactivate the object on GPU """
+
+        self._deactivate()
 
-        return self._deactivate()
-    
-    
     @property
     def handle(self):
-        """ Name of this object in GPU.
-        """
+        """ Name of this object on the 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
+    @property
+    def target(self):
+        """ OpenGL type of object. """
+
+        return self._target
+
+    def _create(self):
+        """ Dummy create method """
+        raise NotImplementedError()
+
+    def _delete(self):
+        """ Dummy delete method """
+        raise NotImplementedError()
+
+    def _activate(self):
+        """ Dummy activate method """
+        raise NotImplementedError()
 
+    def _deactivate(self):
+        """ Dummy deactivate method """
+        raise NotImplementedError()
diff --git a/vispy/gloo/initialize.py b/vispy/gloo/initialize.py
new file mode 100644
index 0000000..13483ae
--- /dev/null
+++ b/vispy/gloo/initialize.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from . import gl
+
+GL_VERTEX_PROGRAM_POINT_SIZE = 34370
+GL_POINT_SPRITE = 34913
+
+
+def gl_initialize():
+    """Initialize GL values
+
+    This method helps standardize GL across desktop and mobile, e.g.
+    by enabling ``GL_VERTEX_PROGRAM_POINT_SIZE`` and ``GL_POINT_SPRITE``.
+    """
+    gl.glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)
+    gl.glEnable(GL_POINT_SPRITE)
diff --git a/vispy/gloo/program.py b/vispy/gloo/program.py
index 9e9c9f5..cda1e48 100644
--- a/vispy/gloo/program.py
+++ b/vispy/gloo/program.py
@@ -1,651 +1,530 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
 # 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 .globject import GLObject
+from .buffer import VertexBuffer, IndexBuffer
 from .shader import VertexShader, FragmentShader
-from vispy.util import is_string
+from .texture import GL_SAMPLER_3D
+from .variable import Uniform, Attribute
+from .wrappers import _check_conversion
+from ..util import logger
 
 
+_known_draw_modes = dict()
+for key in ('points', 'lines', 'line_strip', 'line_loop',
+            'triangles', 'triangle_strip', 'triangle_fan'):
+    x = getattr(gl, 'GL_' + key.upper())
+    _known_draw_modes[key] = x
+    _known_draw_modes[x] = x  # for speed in this case
 
-class ProgramError(RuntimeError):
-    """ Raised when something goes wrong that depens on state that was set 
-    earlier (due to deferred loading).
-    """
-    pass
 
+# ----------------------------------------------------------- Program class ---
+class Program(GLObject):
+    """ Shader program object
 
+    A Program is an object to which shaders can be attached and linked to
+    create the final program.
 
-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.
-    
+    vert : str, VertexShader, or list
+        The vertex shader to be used by this program
+    frag : str, FragmentShader, or list
+        The fragment shader to be used by this program
+    count : int (optional)
+        Number of vertices this program will use. This can be given to
+        initialize a VertexBuffer during Program initialization.
+
+    Notes
+    -----
+    If several shaders are specified, only one can contain the main
+    function. OpenGL ES 2.0 does not support a list of shaders.
     """
-    
-    def __init__(self, vert=None, frag=None):
+
+    # ---------------------------------
+    def __init__(self, vert=None, frag=None, count=0):
         GLObject.__init__(self)
+
+        self._count = count
+        self._buffer = None
         
-        # 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 = []
+        self._need_build = True
         
-        # Containers for uniforms and attributes. 
-        # name -> Variable
-        self._attributes = {}
+        # Init uniforms and 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 = []
+        self._attributes = {}
         
         # 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)
+        self._verts = []
+        if isinstance(vert, (str, VertexShader)):
+            verts = [vert]
+        elif isinstance(vert, (type(None), tuple, list)):
+            verts = vert or []
         else:
-            raise ValueError('Invalid value for VertexShader "%r"' % vert)
-        
+            raise ValueError('Vert must be str, VertexShader or list')
+        # Apply
+        for shader in verts:
+            if isinstance(shader, str):
+                self._verts.append(VertexShader(shader))
+            elif isinstance(shader, VertexShader):
+                if shader not in self._verts:
+                    self._verts.append(shader)
+            else:
+                T = type(shader)
+                raise ValueError('Cannot make a VertexShader of %r.' % T)
+
         # 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)
+        self._frags = []
+        if isinstance(frag, (str, FragmentShader)):
+            frags = [frag]
+        elif isinstance(frag, (type(None), tuple, list)):
+            frags = frag or []
         else:
-            raise ValueError('Invalid value for FragmentShader "%r"' % frag)
+            raise ValueError('Frag must be str, FragmentShader or list')
+        # Apply
+        for shader in frags:
+            if isinstance(shader, str):
+                self._frags.append(FragmentShader(shader))
+            elif isinstance(shader, FragmentShader):
+                if shader not in self._frags:
+                    self._frags.append(shader)
+            else:
+                T = type(shader)
+                raise ValueError('Cannot make a FragmentShader of %r.' % T)
+
+        # Build uniforms and attributes
+        self._create_variables()
+
+        # Build associated structured vertex buffer if count is given
+        if self._count > 0:
+            dtype = []
+            for attribute in self._attributes.values():
+                dtype.append(attribute.dtype)
+            self._buffer = VertexBuffer(np.zeros(self._count, dtype=dtype))
+            self.bind(self._buffer)
+
+    def attach(self, shaders):
+        """ Attach one or several vertex/fragment shaders to the program
         
-        # 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. 
+        Note that GL ES 2.0 only supports one vertex and one fragment 
+        shader.
         
-        Paramaters
+        Parameters
         ----------
-        *shaders : Shader objects
-            The VertexShader or FragmentShader to attach.
         
+        shaders : list of shade objects
+            The shaders to attach.
         """
-        # Tuple or list given?
-        if len(shaders)==1 and isinstance(shaders[0], (list,tuple)):
-            shaders = shaders[0]
-        
-        # Process each shader
+
+        if isinstance(shaders, (VertexShader, FragmentShader)):
+            shaders = [shaders]
         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)
-        
+                self._frags.append(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
+        self._need_create = True
+        self._need_build = True
 
         # Build uniforms and attributes
-        self._build_uniforms()
-        self._build_attributes()
+        self._create_variables()
 
+    def detach(self, shaders):
+        """Detach one or several vertex/fragment shaders from the program.
     
-    def detach(self, *shaders):
-        """ Detach one or several vertex/fragment shaders from the program.
-        
-        Paramaters
+        Parameters
         ----------
-        *shaders : Shader objects
-            The VertexShader or FragmentShader to detach.
+        shaders : list of shade objects
+            The shaders to detach.
         
+        Notes
+        -----
+        We don't need to defer attach/detach shaders since shader deletion
+        takes care of that.
         """
-        # Tuple or list given?
-        if len(shaders)==1 and isinstance(shaders[0], (list,tuple)):
-            shaders = shaders[0]
-        
-        # Process each shader
+
+        if isinstance(shaders, (VertexShader, FragmentShader)):
+            shaders = [shaders]
         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):
+                    raise RuntimeError("Shader is not attached to the program")
+            if 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
-        
+                    raise RuntimeError("Shader is not attached to the program")
+        self._need_build = True
+
         # Build uniforms and attributes
-        self._build_uniforms()
-        self._build_attributes()
-    
-    
-    @property
-    def shaders(self):
-        """ List of shaders associated with this shading program.
+        self._create_variables()
+
+    def _create(self):
         """
-        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.
+        Create the GL program object if needed.
         """
-        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)
+        # Check if program has been created
+        if self._handle <= 0:
+            self._handle = gl.glCreateProgram()
+            if not self._handle:
+                raise RuntimeError("Cannot create program object")
     
+    def _delete(self):
+        logger.debug("GPU: Deleting program")
+        gl.glDeleteProgram(self._handle)
     
-    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]``
+    def _activate(self):
+        """Activate the program as part of current rendering state."""
         
-        """
-        D = {}
+        #logger.debug("GPU: Activating program")
         
-        # Process kwargs
-        D.update(keyword_vars)
+        # Check whether we need to rebuild shaders and create variables
+        if any(s._need_compile for s in self.shaders):
+            self._need_build = True
         
-        # 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.")
+        # Stuff we need to do *before* glUse-ing the program
+        did_build = False
+        if self._need_build:
+            did_build = True
+            self._build()
+            self._need_build = False
         
-        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.')
+        # Go and use the prrogram
+        gl.glUseProgram(self.handle)
         
-        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))
+        # Stuff we need to do *after* glUse-ing the program
+        self._activate_variables()
         
-        # 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 ) )
+        # Validate. We need to validate after textures units get assigned
+        # (glUniform1i() gets called in _update() in variable.py)
+        if did_build:
+            gl.glValidateProgram(self._handle)
+            if not gl.glGetProgramParameter(self._handle, 
+                                            gl.GL_VALIDATE_STATUS):
+                print(gl.glGetProgramInfoLog(self._handle))
+                raise RuntimeError('Program validation error')
     
+    def _deactivate(self):
+        """Deactivate the program."""
+
+        logger.debug("GPU: Deactivating program")
+        gl.glUseProgram(0)
+        self._deactivate_variables()
     
-    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.
+    def _build(self):
         """
-        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.
+        Build (link) the program and checks everything's ok.
+
+        A GL context must be available to be able to build (link)
         """
-        # Get all attributes (There are no attribute in fragment shaders)
-        v_attributes, f_attributes = [], []
+        # Check if we have something to link
+        if not self._verts:
+            raise ValueError("No vertex shader has been given")
+        if not self._frags:
+            raise ValueError("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 vertex and fragment shaders
         for shader in self._verts:
-            v_attributes.extend(shader._get_attributes())
-        attributes = list(set(v_attributes + f_attributes))
+            shader.activate()
+            gl.glAttachShader(self._handle, shader.handle)
+        for shader in self._frags:
+            shader.activate()
+            gl.glAttachShader(self._handle, shader.handle)
+
+        logger.debug("GPU: Creating program")
+
+        # Link the program
+        gl.glLinkProgram(self._handle)
+        if not gl.glGetProgramParameter(self._handle, gl.GL_LINK_STATUS):
+            print(gl.glGetProgramInfoLog(self._handle))
+            raise RuntimeError('Program linking error')
         
-        # Create Attribute objects for each one
-        self._attributes = {}
-        for (name, gtype) in attributes:
-            attribute = Attribute(name, gtype)
-            self._attributes[name] = attribute
-    
+        # Now we know what variable will be used by the program
+        self._enable_variables()
     
-    def _build_uniforms(self):
-        """ Build the uniform objects. 
-        Called when shader is atatched/detached.
+    def _create_variables(self):
+        """ Create the uniform and attribute objects based on the
+        provided GLSL. This method is called when the GLSL is changed.
         """
-        # 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
+        # todo: maybe we want to restore previously set variables, 
+        # so that uniforms and attributes do not have to be set each time
+        # that the shaders are updated. However, we should take into account
+        # that typically all shaders are removed (i.e. no variables are
+        # present) and then the new shaders are added.
+
+        # Build uniforms
         self._uniforms = {}
-        for (name, gtype) in uniforms:
-            uniform = Uniform(name, gtype)
+        count = 0
+        for (name, gtype) in self.all_uniforms:
+            uniform = Uniform(self, name, gtype)
+            # if gtype in (gl.GL_SAMPLER_1D, gl.GL_SAMPLER_2D):
+            if gtype in (gl.GL_SAMPLER_2D, GL_SAMPLER_3D):
+                uniform._unit = count
+                count += 1
             self._uniforms[name] = uniform
         
+        # Build attributes
+        self._attributes = {}
+        dtype = []
+        for (name, gtype) in self.all_attributes:
+            attribute = Attribute(self, name, gtype)
+            self._attributes[name] = attribute
+            dtype.append(attribute.dtype)
+    
+    def _enable_variables(self):  # previously _update
+        """ Enable the uniform and attribute objects that will actually be
+        used by the Program. i.e. variables that are optimised out are
+        disabled. This method is called after the program has been buid.
+        """
+        # Enable uniforms
+        active_uniforms = [name for (name, gtype) in self.active_uniforms]
+        for uniform in self._uniforms.values():
+            uniform.enabled = uniform.name in active_uniforms
+        # Enable attributes
+        active_attributes = [name for (name, gtype) in self.active_attributes]
+        for attribute in self._attributes.values():
+            attribute.enabled = attribute.name in active_attributes
     
-    def _mark_active_attributes(self):
-        """ Mark which attributes are active and set the location.
-        Called after linking. 
+    def _activate_variables(self):
+        """ Activate the uniforms and attributes so that the Program
+        can use them. This method is called when the Program gets activated.
         """
+        for uniform in self._uniforms.values():
+            if uniform.enabled:
+                uniform.activate()
+        for attribute in self._attributes.values():
+            if attribute.enabled:
+                attribute.activate()
 
-        count = gl.glGetProgramiv(self.handle, gl.GL_ACTIVE_ATTRIBUTES)
+    def _deactivate_variables(self):
+        """ Deactivate all enabled uniforms and attributes. This method
+        gets called when the Program gets deactivated.
+        """
+        for uniform in self._uniforms.values():
+            if uniform.enabled:
+                uniform.deactivate()
+        for attribute in self._attributes.values():
+            if attribute.enabled:
+                attribute.deactivate()
+    
+    def bind(self, data):
+        """ Bind a VertexBuffer that has structured data
         
+        Parameters
+        ----------
+        data : VertexBuffer
+            The vertex buffer to bind. The field names of the array
+            are mapped to attribute names in GLSL.
+        """
+        # Check
+        if not isinstance(data, VertexBuffer):
+            raise ValueError('Program.bind() requires a VertexBuffer.')
+        # Apply
+        for name in data.dtype.names:
+            if name in self._attributes.keys():
+                self._attributes[name].set_data(data[name])
+            else:
+                logger.warning("%s has not been bound" % name)
+
+    def __setitem__(self, name, data):
+        try:
+            if name in self._uniforms.keys():
+                self._uniforms[name].set_data(data)
+            elif name in self._attributes.keys():
+                self._attributes[name].set_data(data)
+            else:
+                raise KeyError("Unknown uniform or attribute %s" % name)
+        except Exception:
+            logger.error("Could not set variable '%s' with value %s" % 
+                         (name, data))
+            raise
+
+    def __getitem__(self, name):
+        if name in self._uniforms.keys():
+            return self._uniforms[name].data
+        elif name in self._attributes.keys():
+            return self._attributes[name].data
+        else:
+            raise KeyError("Unknown uniform or attribute %s" % name)
+
+    @property
+    def all_uniforms(self):
+        """ Program uniforms obtained from shaders code """
+
+        uniforms = []
+        for shader in self._verts:
+            uniforms.extend(shader.uniforms)
+        for shader in self._frags:
+            uniforms.extend(shader.uniforms)
+        uniforms = list(set(uniforms))
+        return uniforms
+
+    @property
+    def active_uniforms(self):
+        """ Program active uniforms obtained from GPU """
+
+        count = gl.glGetProgramParameter(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+)\])""")
-        
-        # Find active attributes
-        self._active_attributes = {}
+        regex = re.compile("""(?P<name>\w+)\s*(\[(?P<size>\d+)\])\s*""")
+        uniforms = []
         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, size, gtype = gl.glGetActiveUniform(self.handle, i)
+            # This checks if the uniform 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
+            # 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_attributes[name] = loc
+                        name = '%s[%d]' % (m.group('name'), i)
+                        uniforms.append((name, gtype))
             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. 
-        """
+                uniforms.append((name, gtype))
+
+        return uniforms
+
+    @property
+    def inactive_uniforms(self):
+        """ Program inactive uniforms obtained from GPU """
+
+        active_uniforms = self.active_uniforms
+        inactive_uniforms = self.all_uniforms
+        for uniform in active_uniforms:
+            if uniform in inactive_uniforms:
+                inactive_uniforms.remove(uniform)
+        return inactive_uniforms
+
+    @property
+    def all_attributes(self):
+        """ Program attributes obtained from shaders code """
+
+        attributes = []
+        for shader in self._verts:
+            attributes.extend(shader.attributes)
+        # No attribute in fragment shaders
+        attributes = list(set(attributes))
+        return attributes
+
+    @property
+    def active_attributes(self):
+        """ Program active attributes obtained from GPU """
+
+        count = gl.glGetProgramParameter(self.handle, gl.GL_ACTIVE_ATTRIBUTES)
+        attributes = []
 
-        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 = {}
+        regex = re.compile("""(?P<name>\w+)\s*(\[(?P<size>\d+)\])""")
+
         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, size, gtype = gl.glGetActiveAttrib(self.handle, i)
+
+            # This checks if the attribute 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
+            # 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_uniforms[name] = loc
+                        name = '%s[%d]' % (m.group('name'), i)
+                        attributes.append((name, gtype))
             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.
-        
+                attributes.append((name, gtype))
+        return attributes
+
+    @property
+    def inactive_attributes(self):
+        """ Program inactive attributes obtained from GPU """
+
+        active_attributes = self.active_attributes
+        inactive_attributes = self.all_attributes
+        for attribute in active_attributes:
+            if attribute in inactive_attributes:
+                inactive_attributes.remove(attribute)
+        return inactive_attributes
+
+    @property
+    def shaders(self):
+        """ List of shaders currently attached to this program """
+
+        shaders = []
+        shaders.extend(self._verts)
+        shaders.extend(self._frags)
+        return shaders
+
+    def draw(self, mode=gl.GL_TRIANGLES, indices=None, check_error=True):
+        """ Draw the attribute arrays in the specified mode.
+
         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.
+        mode : str | GL_ENUM
+            GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP,
+            GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
+        indices : array
+            Array of indices to draw.
+        check_error:
+            Check error after draw.
         """
-        
-        # 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)
-        
+        mode = _check_conversion(mode, _known_draw_modes)
+        self.activate()
+        if check_error:  # need to do this after activating, too
+            gl.check_error('Check after draw activation')
+
+        # WARNING: The "list" of values from a dict is not a list (py3k)
+        attributes = list(self._attributes.values())
+        sizes = [a.size for a in attributes]
+        if len(attributes) < 1:
+            raise RuntimeError('Must have at least one attribute')
+        if not all(s == sizes[0] for s in sizes[1:]):
+            msg = '\n'.join(['%s: %s' % (str(a), a.size) for a in attributes])
+            raise RuntimeError('All attributes must have the same size, got:\n'
+                               '%s' % msg)
+
+        if isinstance(indices, IndexBuffer):
+            indices.activate()
+            logger.debug("Program drawing %d %r (using index buffer)", 
+                         indices.size, mode)
+            gltypes = {np.dtype(np.uint8): gl.GL_UNSIGNED_BYTE,
+                       np.dtype(np.uint16): gl.GL_UNSIGNED_SHORT,
+                       np.dtype(np.uint32): gl.GL_UNSIGNED_INT}
+            gl.glDrawElements(mode, indices.size, gltypes[indices.dtype], None)
+            indices.deactivate()
+        elif indices is None:
+            #count = (count or attributes[0].size) - first
+            first = 0
+            count = attributes[0].size
+            logger.debug("Program drawing %d %r (no index buffer)", 
+                         count, mode)
+            gl.glDrawArrays(mode, first, count)
         else:
-            raise ValueError('Given subset is of invalid type: %r.' % type(subset))
-        
-        # Clean up
-        for enum in need_enabled:
-            gl.glDisable(enum)
+            raise TypeError("Invalid index: %r (must be IndexBuffer)" %
+                            indices)
+
+        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
+        self.deactivate()
+
+        # Check ok
+        if check_error:
+            gl.check_error('Check after drawing completes')
diff --git a/vispy/gloo/shader.py b/vispy/gloo/shader.py
index 8750761..c67bcf6 100644
--- a/vispy/gloo/shader.py
+++ b/vispy/gloo/shader.py
@@ -1,49 +1,28 @@
 # -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# Copyright (c) 2013, Vispy Development Team. All rights reserved.
+# Copyright (c) 2014, 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
+import os.path
 
-from vispy.util import is_string
 from . import gl
-from . import GLObject
-
-
-
-# ------------------------------------------------------- class ShaderError ---
-class ShaderError(RuntimeError):
-    """ Shader error class """
-    pass
+from ..util import logger
+from .globject import GLObject
+from .texture import GL_SAMPLER_3D
 
 
-
-
-
-# ------------------------------------------------------------ class Shader ---
+# ------------------------------------------------------------ Shader class ---
 class Shader(GLObject):
-    """ Abstract shader class.
+    """ Abstract shader class
+    
+    Parameters
+    ----------
+
+    code: str
+        code can be a filename or the actual code
     """
 
-    # Conversion of known uniform and attribute types to GL constants
     _gtypes = {
         'float':       gl.GL_FLOAT,
         'vec2':        gl.GL_FLOAT_VEC2,
@@ -60,231 +39,145 @@ class Shader(GLObject):
         'mat2':        gl.GL_FLOAT_MAT2,
         'mat3':        gl.GL_FLOAT_MAT3,
         'mat4':        gl.GL_FLOAT_MAT4,
+        #        'sampler1D':   gl.GL_SAMPLER_1D,
         '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.
-        """
+        'sampler3D':   GL_SAMPLER_3D,
+    }
 
+    def __init__(self, target, code=None):
         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.')
+            raise ValueError("Shader target must be vertex or fragment")
+
         self._target = target
-        
-        # For auto-enabling point sprites
-        self._need_enabled = set()
-        
-        # Set code
         self._code = None
         self._source = None
+        self._need_compile = False
+        self.__clean_code = None
+        self._attributes = None
+        self._uniforms = 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
+            self.code = code
+
+    @property
+    def code(self):
+        """ Shader source code """
+        return self._code
+
+    @code.setter
+    def code(self, code):
+        """ Shader source code """
         if os.path.isfile(code):
-            with open(code, 'rb') as file:
-                self._code   = file.read().decode('utf-8')
+            with open(code, 'rt') as file:
+                self._code = file.read()
                 self._source = os.path.basename(code)
         else:
-            self._code   = code
+            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
-    
-    
+        self._need_compile = True
+        self.__clean_code = None
+        self._attributes = None
+        self._uniforms = None
+
     @property
     def source(self):
-        """ The source of the code for this shader 
-        (as in where it came from, not the source code).
-        """
+        """ Shader source (string or filename) """
         return self._source
     
+    def _activate(self):
+        # shaders do not need any kind of (de)activation
+        # in the sense of glActiveSomething()
+        
+        # Recompile if necessary
+        if self._need_compile:
+            self._compile_shader()
+            self._need_compile = False
     
-    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 _deactivate(self):
+        pass  # shaders do not need any kind of (de)activation
     
-    def _get_uniforms(self):
-        """
-        Extract uniforms (name and type) from code.
-        """
+    def _create(self):
+        """ Create the shader object on the GPU """
 
-        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))
+        # Check if we have something to compile
+        if not self._code:
+            raise RuntimeError("No code has been given")
 
-        return uniforms
-    
-    
-    def _create(self):
-        """
-        Create the shader.
-        """
+        # Create and check that shader object has been created
         self._handle = gl.glCreateShader(self._target)
-    
-    
-    def _delete(self):
-        """
-        Delete the shader.
-        """
+        if self._handle <= 0:
+            raise RuntimeError("Cannot create shader object")
 
-        gl.glDeleteShader(self._handle)
+    def _compile_shader(self):
+        """ Compile the source and checks everything's ok """
+        # Set shader source
+        gl.glShaderSource(self._handle, self._code)
 
-    
-    def _update(self):
-        """
-        Compile the shader.
-        """
+        logger.debug("GPU: Creating 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?
+        # Actual compilation
         gl.glCompileShader(self._handle)
-
-        # Check the compile status
-        status = gl.glGetShaderiv(self._handle, gl.GL_COMPILE_STATUS)
+        status = gl.glGetShaderParameter(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)
+            raise RuntimeError("Shader compilation error in %r:\n%s" % 
+                               (self, errormsg))
+
+    def _delete(self):
+        """ Delete shader from GPU memory (if it was present). """
 
+        gl.glDeleteShader(self._handle)
 
     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
         """
+        error = str(error)
         
         # 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)
-        
+        m = re.match(r'(\d+)\((\d+)\)\s*:\s(.*)', error)
+        if m:
+            return int(m.group(2)), m.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 )
-    
+        m = re.match(r'ERROR:\s(\d+):(\d+):\s(.*)', error)
+        if m:
+            return int(m.group(2)), m.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)
-        
+        m = re.match(r'(\d+):(\d+)\((\d+)\):\s(.*)', error)
+        if m:
+            return int(m.group(2)), m.group(4)
+
         # Other ...
         return None, error
 
-
     def _get_error(self, errors, indentation=0):
-        """
-        Get error and show the faulty line + some context
-        
+        """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
+            An error string as returned by the compilation process
+        indentation : int
+            Number of spaces to indent the found error.
         """
-        
         # 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()
@@ -296,48 +189,96 @@ class Shader(GLObject):
                 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])
-        
+                if linenr > 0 and linenr < len(lines):
+                    results.append('  %s' % lines[linenr - 1])
+
         # Add indentation and return
-        results = [' '*indentation + r for r in results]
+        results = [' ' * indentation + r for r in results]
         return '\n'.join(results)
 
+    @property
+    def uniforms(self):
+        """ Shader uniforms obtained from source code """
+        if self._uniforms is None:
+            uniforms = []
+            regex = re.compile("""\s*uniform\s+(?P<type>\w+)\s+"""
+                               """(?P<name>\w+)\s*(\[(?P<size>\d+)\])?\s*;""",
+                               flags=re.MULTILINE)
+            for m in re.finditer(regex, self._clean_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))
+            self._uniforms = uniforms
+        return self._uniforms
+
+    @property
+    def attributes(self):
+        """ Shader attributes obtained from source code """
+        if self._attributes is None:
+            attributes = []
+            regex = re.compile("""\s*attribute\s+(?P<type>\w+)\s+"""
+                               """(?P<name>\w+)\s*(\[(?P<size>\d+)\])?\s*;""",
+                               flags=re.MULTILINE)
+            for m in re.finditer(regex, self._clean_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)
+                        attributes.append((name, gtype))
+                else:
+                    attributes.append((m.group('name'), gtype))
+            self._attributes = attributes
+        return self._attributes
+
+    @property
+    def _clean_code(self):
+        # Return code with comments stripped
+        if self.__clean_code is None:
+            self.__clean_code = re.sub(r'(.*)(//.*)', r'\1', self._code, re.M)
+        return self.__clean_code
+
 
-# ------------------------------------------------------ class VertexShader ---
+# ------------------------------------------------------ VertexShader class ---
 class VertexShader(Shader):
-    """ Vertex shader class. Inherits :class:`shader.Shader`.
+    """ Vertex shader object
     
     Parameters
     ----------
-    code : str
-        The GLSL source code, or a filename that contains the code.
-   
+
+    code: str
+        code can be a filename or the actual code
     """
 
     def __init__(self, code=None):
-        """
-        Create the shader.
-        """
-        
         Shader.__init__(self, gl.GL_VERTEX_SHADER, code)
 
+    def __repr__(self):
+        return "Vertex Shader %d (%s)" % (self._id, self._source)
 
 
-# ---------------------------------------------------- class FragmentShader ---
+# ---------------------------------------------------- FragmentShader class ---
 class FragmentShader(Shader):
-    """ Fragment shader class. Inherits :class:`shader.Shader`.
+    """ Fragment shader object
     
     Parameters
     ----------
-    code : str
-        The GLSL source code, or a filename that contains the code.
-    
+
+    code: str
+        code can be a filename or the actual code
     """
 
     def __init__(self, code=None):
-        """
-        Create the shader.
-        """
-        
         Shader.__init__(self, gl.GL_FRAGMENT_SHADER, code)
+
+    def __repr__(self):
+        return "Fragment Shader %d (%s)" % (self._id, self._source)
diff --git a/vispy/shaders/__init__.py b/vispy/gloo/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/gloo/tests/__init__.py
diff --git a/vispy/gloo/tests/test_buffer.py b/vispy/gloo/tests/test_buffer.py
index f940c7e..aa1d54f 100644
--- a/vispy/gloo/tests/test_buffer.py
+++ b/vispy/gloo/tests/test_buffer.py
@@ -1,410 +1,452 @@
+# -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team
-# All rights reserved.
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
 # -----------------------------------------------------------------------------
 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
 
+from vispy.util import use_log_level
+from vispy.gloo import gl
+from vispy.gloo.buffer import Buffer, DataBuffer, VertexBuffer, IndexBuffer
 
 
 # -----------------------------------------------------------------------------
 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)
+    # Default init
+    # ------------
+    def test_init_default(self):
+        B = Buffer()
+        assert B._target == gl.GL_ARRAY_BUFFER
+        assert B._handle == -1
+        assert B._need_create is True
+        assert B._need_delete is False
+        assert B._nbytes == 0
+        assert B._usage == gl.GL_DYNAMIC_DRAW
+
+    # Unknown target
+    # --------------
+    def test_init_wrong_target(self):
+        # with self.assertRaises(ValueError):
+        #    B = Buffer(target=-1)
+        self.assertRaises(ValueError, Buffer, target=-1)
+
+    # No data
+    # -------
+    def test_init_no_data(self):
+        B = Buffer()
+        assert len(B._pending_data) == 0
+
+    # Data
+    # ----
+    def test_init_with_data(self):
+        data = np.zeros(100)
+        B = Buffer(data=data)
+        assert len(B._pending_data) == 1
+
+    # Check setting the whole buffer clear pending operations
+    # -------------------------------------------------------
+    def test_set_whole_data(self):
+        data = np.zeros(100)
+        B = Buffer(data=data)
+        B.set_data(data=data)
+        assert len(B._pending_data) == 1
+
+    # Check stored data is data
+    # -------------------------
+    def test_data_storage(self):
+        data = np.zeros(100)
+        B = Buffer(data=data)
+        B.set_data(data=data[:50], copy=False)
+        assert B._pending_data[-1][0].base is data
+
+    # Check stored data is a copy
+    # ----------------------------
+    def test_data_copy(self):
+        data = np.zeros(100)
+        B = Buffer(data=data)
+        B.set_data(data=data[:50], copy=True)
+        assert B._pending_data[-1][0].base is not data
+
+    # Check setting oversized data
+    # ----------------------------
+    def test_oversized_data(self):
+        data = np.zeros(10)
+        B = Buffer(data=data)
+        # with self.assertRaises(ValueError):
+        #    B.set_data(np.ones(20))
+        self.assertRaises(ValueError, B.set_subdata, np.ones(20), offset=0)
+
+    # Check negative offset
+    # ---------------------
+    def test_negative_offset(self):
+        data = np.zeros(10)
+        B = Buffer(data=data)
+        # with self.assertRaises(ValueError):
+        #    B.set_data(np.ones(1), offset=-1)
+        self.assertRaises(ValueError, B.set_subdata, np.ones(1), offset=-1)
+
+    # Check offlimit offset
+    # ---------------------
+    def test_offlimit_offset(self):
+        data = np.zeros(10)
+        B = Buffer(data=data)
+        # with self.assertRaises(ValueError):
+        #    B.set_data(np.ones(1), offset=10 * data.dtype.itemsize)
+        self.assertRaises(ValueError, B.set_subdata,
+                          np.ones(1), offset=10 * data.dtype.itemsize)
+
+    # Buffer size
+    # -----------
+    def test_buffer_size(self):
+        data = np.zeros(10)
+        B = Buffer(data=data)
+        assert B.nbytes == data.nbytes
+
+    # Resize
+    # ------
+    def test_buffer_resize(self):
+        data = np.zeros(10)
+        B = Buffer(data=data)
+        data = np.zeros(20)
+        B.set_data(data)
+        assert B.nbytes == data.nbytes
 
-        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
+# -----------------------------------------------------------------------------
+class DataBufferTest(unittest.TestCase):
+
+    # Default init
+    # ------------
+    def test_default_init(self):
+        # Check default storage and copy flags
+        data = np.ones(100)
+        B = DataBuffer(data)
+        assert B._store is True
+        assert B._copied is False
+        assert B.nbytes == data.nbytes
+        assert B.offset == 0
+        assert B.size == 100
+        assert B.itemsize == data.itemsize
+        assert B.stride == data.itemsize
+        assert B.dtype == data.dtype
+
+    # Default init with structured data
+    # ---------------------------------
+    def test_structured_init(self):
+        # Check structured type
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data)
+        assert B.nbytes == data.nbytes
+        assert B.offset == 0
+        assert B.size == 10
+        assert B.itemsize == data.itemsize
+        assert B.stride == data.itemsize
+        assert B.dtype == data.dtype
+
+    # CPU storage
+    # ------------
+    def test_storage(self):
+        data = np.ones(100)
+        B = DataBuffer(data, store=True)
+        assert B.data.base is data
+
+    # Use CPU storage but make a local copy for storage
+    # -------------------------------------------------
+    def test_storage_copy(self):
+        data = np.ones(100, np.float32)
+        B = DataBuffer(data.copy(), store=True)  # we got rid of copy arg
+        assert B.data is not None
+        assert B.data is not data
+        assert B.stride == 4
+
+    # No CPU storage
+    # --------------
+    def test_no_storage_copy(self):
+        data = np.ones(100, np.float32)
+        B = DataBuffer(data, store=False)
+        assert B.data is None
+        assert B.stride == 4
+
+    # Empty init (not allowed)
+    # ------------------------
+    def test_empty_init(self):
+        # with self.assertRaises(ValueError):
+        #    B = DataBuffer()
+        self.assertRaises(ValueError, DataBuffer)
+
+    # Wrong storage
+    # -------------
+    def test_non_contiguous_storage(self):
+        # Ask to have CPU storage and to use data as storage
+        # Not possible since data[::2] is not contiguous
+        data = np.ones(100, np.float32)
+        data_given = data[::2]
+        
+        with use_log_level('warning', record=True, print_msg=False) as l:
+            B = DataBuffer(data_given, store=True)
+        assert len(l) == 1
+        assert B._data is not data_given
+        assert B.stride == 4
+        
+        B = DataBuffer(data_given, store=False)
+        assert B._data is not data_given
+        assert B.stride == 4*2
+
+    # Get buffer field
+    # ----------------
+    def test_getitem_field(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data)
+
+        Z = B["position"]
+        assert Z.nbytes == 10 * 3 * np.dtype(np.float32).itemsize
+        assert Z.offset == 0
+        assert Z.size == 10
+        assert Z.itemsize == 3 * np.dtype(np.float32).itemsize
+        assert Z.stride == (3 + 2 + 4) * np.dtype(np.float32).itemsize
+        assert Z.dtype == (np.float32, 3)
+
+        Z = B["texcoord"]
+        assert Z.nbytes == 10 * 2 * np.dtype(np.float32).itemsize
+        assert Z.offset == 3 * np.dtype(np.float32).itemsize
+        assert Z.size == 10
+        assert Z.itemsize == 2 * np.dtype(np.float32).itemsize
+        assert Z.stride == (3 + 2 + 4) * np.dtype(np.float32).itemsize
+        assert Z.dtype == (np.float32, 2)
+
+        Z = B["color"]
+        assert Z.nbytes == 10 * 4 * np.dtype(np.float32).itemsize
+        assert Z.offset == (2 + 3) * np.dtype(np.float32).itemsize
+        assert Z.size == 10
+        assert Z.itemsize == 4 * np.dtype(np.float32).itemsize
+        assert Z.stride == (3 + 2 + 4) * np.dtype(np.float32).itemsize
+        assert Z.dtype == (np.float32, 4)
+
+    # Get item via index
+    # ------------------
+    def test_getitem_index(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data)
+        Z = B[0:1]
+        assert Z.nbytes == 1 * (3 + 2 + 4) * np.dtype(np.float32).itemsize
+        assert Z.offset == 0
+        assert Z.size == 1
+        assert Z.itemsize == (3 + 2 + 4) * np.dtype(np.float32).itemsize
+        assert Z.stride == (3 + 2 + 4) * np.dtype(np.float32).itemsize
+        assert Z.dtype == B.dtype
+
+    # View get invalidated when base is resized
+    # -----------------------------------------
+    def test_invalid_view_after_resize(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data)
+        Z = B[5:]
+        B.resize_bytes(5)
+        assert Z._valid is False
+
+    # View get invalidated after setting oversized data
+    # -------------------------------------------------
+    def test_invalid_view_after_set_data(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data)
+        Z = B[5:]
+        B.set_data(np.zeros(15, dtype=dtype))
+        assert Z._valid is False
+
+    # Set data on base buffer : ok
+    # ----------------------------
+    def test_set_data_base(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data, store=True)
+        B.set_data(data)
+        assert len(B._pending_data) == 1
+
+    # Set data on view buffer : error
+    # -------------------------------
+    def test_set_data_base_view(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data, store=True)
+        # set_data on field is not allowed because set_data
+        # can result in a buffer resize
+
+        # with self.assertRaises(ValueError):
+        #    B['position'].set_data(data)
+        Z = B['position']
+        self.assertRaises(ValueError, Z.set_data, data)
+
+    # Check set_data using offset in data buffer
+    # ------------------------------------------
+    def test_set_data_offset(self):
         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)
-
+        subdata = data[:10]
+        
+        B = DataBuffer(data)
+        B.set_subdata(subdata, offset=10)
+        offset = B._pending_data[-1][2]
+        assert offset == 10*4
+
+    # Setitem + broadcast
+    # ------------------------------------------------------
+    def test_setitem_broadcast(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data, store=True)
+        B['position'] = 1, 2, 3
+        assert np.allclose(data['position'].ravel(), np.resize([1, 2, 3], 30))
+
+    # Setitem ellipsis
+    # ------------------------------------------------------
+    def test_setitem_ellipsis(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data1 = np.zeros(10, dtype=dtype)
+        data2 = np.ones(10, dtype=dtype)
+        B = DataBuffer(data1, store=True)
+        B[...] = data2
+        assert np.allclose(data1['position'], data2['position'])
+        assert np.allclose(data1['texcoord'], data2['texcoord'])
+        assert np.allclose(data1['color'], data2['color'])
+
+    # Set every 2 item
+    # ------------------------------------------------------
+    def test_setitem_strided(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data1 = np.zeros(10, dtype=dtype)
+        data2 = np.ones(10, dtype=dtype)
+        B = DataBuffer(data1, store=True)
+        B[::2] = data2[::2]
+        assert np.allclose(data1['position'][::2], data2['position'][::2])
+        assert np.allclose(data1['texcoord'][::2], data2['texcoord'][::2])
+        assert np.allclose(data1['color'][::2], data2['color'][::2])
+
+    # Set half the array
+    # ------------------------------------------------------
+    def test_setitem_half(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data1 = np.zeros(10, dtype=dtype)
+        data2 = np.ones(10, dtype=dtype)
+        B = DataBuffer(data1, store=True)
+        B[:5] = data2[:5]
+        assert np.allclose(data1['position'][:5], data2['position'][:5])
+        assert np.allclose(data1['texcoord'][:5], data2['texcoord'][:5])
+        assert np.allclose(data1['color'][:5], data2['color'][:5])
+        assert len(B._pending_data) == 2
+
+    # Set field without storage: error
+    # --------------------------------
+    def test_setitem_field_no_storage(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data, store=False)
+        # with self.assertRaises(ValueError):
+        #    B['position'] = 1, 2, 3
+        self.assertRaises(ValueError,  B.__setitem__, 'position', (1, 2, 3))
+
+    # Set every 2 item without storage:  error
+    # ----------------------------------------
+    def test_every_two_item_no_storage(self):
+        dtype = np.dtype([('position', np.float32, 3),
+                          ('texcoord', np.float32, 2),
+                          ('color',    np.float32, 4)])
+        data = np.zeros(10, dtype=dtype)
+        B = DataBuffer(data, store=False)
+        # with self.assertRaises(ValueError):
+        #    B[::2] = data[::2]
+        s = slice(None, None, 2)
+        self.assertRaises(ValueError, B.__setitem__, s, data[::2])
+
+    # Resize
+    # ------
+    def test_resize(self):
+        data = np.zeros(10)
+        B = DataBuffer(data=data)
+        data = np.zeros(20)
+        B.set_data(data)
+        assert B.nbytes == data.nbytes
+
+    # Resize not allowed using ellipsis
+    # --------------------------------
+    def test_no_resize_ellipsis(self):
+        data = np.zeros(10)
+        B = DataBuffer(data=data)
+        data = np.zeros(30)
+        # with self.assertRaises(ValueError):
+        #    B[...] = data
+        self.assertRaises(ValueError, B.__setitem__, Ellipsis, 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)
-
+    # VertexBuffer allowed base types
+    # -------------------------------
+    def test_init_allowed_dtype(self):
+        for dtype in (np.uint8, np.int8, np.uint16, np.int16, np.float32):
+            V = VertexBuffer(dtype=dtype)
+            names = V.dtype.names
+            assert V.dtype[names[0]].base == dtype
+            assert V.dtype[names[0]].shape == ()
+
+    # VertexBuffer not allowed base types
+    # -----------------------------------
+    def test_init_not_allowed_dtype(self):
+        for dtype in (np.uint32, np.int32, np.float64):
+            # with self.assertRaises(TypeError):
+            #    V = VertexBuffer(dtype=dtype)
+            self.assertRaises(TypeError, VertexBuffer, dtype=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)
 
+class IndexBufferTest(unittest.TestCase):
 
+    # IndexBuffer allowed base types
+    # ------------------------------
+    def test_init_allowed_dtype(self):
+        for dtype in (np.uint8, np.uint16, np.uint32):
+            V = IndexBuffer(dtype=dtype)
+            assert V.dtype == dtype
+
+    # IndexBuffer not allowed base types
+    # -----------------------------------
+    def test_init_not_allowed_dtype(self):
+        for dtype in (np.int8, np.int16, np.int32,
+                      np.float16, np.float32, np.float64):
+            # with self.assertRaises(TypeError):
+            #    V = IndexBuffer(dtype=dtype)
+            self.assertRaises(TypeError, IndexBuffer, dtype=dtype)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/vispy/gloo/tests/test_framebuffer.py b/vispy/gloo/tests/test_framebuffer.py
deleted file mode 100644
index 9f9c0ce..0000000
--- a/vispy/gloo/tests/test_framebuffer.py
+++ /dev/null
@@ -1,206 +0,0 @@
-# -----------------------------------------------------------------------------
-# 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
index 2297edc..de4fd12 100644
--- a/vispy/gloo/tests/test_globject.py
+++ b/vispy/gloo/tests/test_globject.py
@@ -1,217 +1,25 @@
 # -*- 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.
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
 # -----------------------------------------------------------------------------
 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 ()
-
+from vispy.gloo.globject import GLObject
 
 
+# -----------------------------------------------------------------------------
 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
-    
-    
- 
-
 
+    # Default init
+    # ------------
+    def test_init_default(self):
+        O = GLObject()
+
+        assert O._handle == -1
+        assert O._target is None
+        assert O._need_create is True
+        assert O._need_delete is False
+        assert O._id > 0
+        assert O._id == GLObject._idcount
 if __name__ == "__main__":
     unittest.main()
diff --git a/vispy/gloo/tests/test_program.py b/vispy/gloo/tests/test_program.py
index c9657bd..c7903de 100644
--- a/vispy/gloo/tests/test_program.py
+++ b/vispy/gloo/tests/test_program.py
@@ -1,28 +1,20 @@
+# -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team
-# All rights reserved.
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
 # -----------------------------------------------------------------------------
 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
+from vispy.gloo.shader import VertexShader, FragmentShader
 
 
-
-
-# -----------------------------------------------------------------------------
 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._handle == -1
         assert program.shaders == []
 
     def test_delete_no_context(self):
@@ -30,13 +22,13 @@ class ProgramTest(unittest.TestCase):
         program.delete()
 
     def test_init_from_string(self):
-        program = Program("A","B")
+        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"))
+        program = Program(VertexShader("A"), FragmentShader("B"))
         assert len(program.shaders) == 2
         assert program.shaders[0].code == "A"
         assert program.shaders[1].code == "B"
@@ -44,25 +36,23 @@ class ProgramTest(unittest.TestCase):
     def test_unique_shader(self):
         vert = VertexShader("A")
         frag = FragmentShader("B")
-        program = Program([vert,vert],[frag,frag,frag])
+        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
+        program = Program(vert, frag)
+        assert ("A", gl.GL_FLOAT) in program.all_uniforms
+        assert ("B", gl.GL_FLOAT_VEC4) in program.all_uniforms
+        assert len(program.all_uniforms) == 2
 
     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
-    
+        program = Program(vert, frag)
+        assert program.all_attributes == [("A", gl.GL_FLOAT)]
+
     def test_attach(self):
         vert = VertexShader("A")
         frag = FragmentShader("B")
@@ -84,71 +74,22 @@ class ProgramTest(unittest.TestCase):
         vert = VertexShader("A")
         frag = FragmentShader("B")
 
-        program = Program(vert = vert)
-        with self.assertRaises(RuntimeError):
-            program.activate()
+        program = Program(vert=vert)
+        program._need_create = False  # fool program that it already exists
+        self.assertRaises(ValueError, program.activate)
 
-        program = Program(frag = frag)
-        with self.assertRaises(RuntimeError):
-            program.activate()
+        program = Program(frag=frag)
+        program._need_create = False  # fool program that it already exists
+        self.assertRaises(ValueError, 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
+        program = Program(vert, frag)
+        #with self.assertRaises(ValueError):
+        #    program["A"] = 1
+        self.assertRaises(KeyError, program.__setitem__, "A", 1)
 
 
 if __name__ == "__main__":
diff --git a/vispy/gloo/tests/test_shader.py b/vispy/gloo/tests/test_shader.py
index a0b017c..46a4565 100644
--- a/vispy/gloo/tests/test_shader.py
+++ b/vispy/gloo/tests/test_shader.py
@@ -1,14 +1,13 @@
+# -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team
-# All rights reserved.
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
 # -----------------------------------------------------------------------------
 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
 
+from vispy.gloo import gl
+from vispy.gloo.shader import VertexShader, FragmentShader
+from vispy.testing import assert_in, assert_not_in
 
 
 # -----------------------------------------------------------------------------
@@ -18,6 +17,7 @@ class VertexShaderTest(unittest.TestCase):
         shader = VertexShader()
         assert shader._target == gl.GL_VERTEX_SHADER
 
+
 # -----------------------------------------------------------------------------
 class FragmentShaderTest(unittest.TestCase):
 
@@ -31,11 +31,9 @@ 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
+        assert shader._handle == -1
+        assert shader.code is None
+        assert shader.source is None
 
     def test_sourcecode(self):
         code = "/* Code */"
@@ -43,17 +41,11 @@ class ShaderTest(unittest.TestCase):
         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):
+        #with self.assertRaises(RuntimeError):
         #    shader.activate()
+        self.assertRaises(RuntimeError, shader.activate)
 
     def test_delete_no_context(self):
         shader = VertexShader()
@@ -61,29 +53,38 @@ class ShaderTest(unittest.TestCase):
 
     def test_uniform_float(self):
         shader = VertexShader("uniform float color;")
-        uniforms = shader._get_uniforms()
-        assert uniforms == [ ("color", gl.GL_FLOAT) ]
- 
+        assert shader.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) ]
- 
+        assert shader.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)  ]
- 
+        assert shader.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) ]
- 
+        assert shader.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) ]
-
+        assert shader.attributes == [("color", gl.GL_FLOAT_VEC4)]
+        
+    def test_ignore_comments(self):
+        shader = VertexShader("""
+            attribute vec4 color; attribute float x;
+            // attribute float y;
+            attribute float z; //attribute float w;
+        """)
+        names = [attr[0] for attr in shader.attributes]
+        assert_in("color", names)
+        assert_in("x", names)
+        assert_in("z", names)
+        assert_not_in("y", names)
+        assert_not_in("w", names)
+
+        
 if __name__ == "__main__":
     unittest.main()
diff --git a/vispy/gloo/tests/test_texture.py b/vispy/gloo/tests/test_texture.py
index f6eaf9f..cc6b59e 100644
--- a/vispy/gloo/tests/test_texture.py
+++ b/vispy/gloo/tests/test_texture.py
@@ -1,260 +1,603 @@
+# -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team
-# All rights reserved.
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
 # -----------------------------------------------------------------------------
 import unittest
 import numpy as np
-from vispy.gloo import gl
-from vispy.gloo.texture import Texture, Texture2D, Texture3D
 
+from vispy.util import use_log_level
+from vispy.gloo import Texture2D, Texture3D, gl
+from vispy.testing import requires_pyopengl
 
+# here we test some things that will be true of all Texture types:
+Texture = Texture2D
 
-class TextureBasetests:
-    
+
+# ----------------------------------------------------------------- Texture ---
+class TextureTest(unittest.TestCase):
+
+    # No data, no dtype : forbidden
+    # ---------------------------------
+    def test_init_none(self):
+        self.assertRaises(ValueError, Texture)
+
+    # Data only
+    # ---------------------------------
+    def test_init_data(self):
+        data = np.zeros((10, 10, 3), dtype=np.uint8)
+        T = Texture(data=data)
+        assert T._shape == (10, 10, 3)
+        assert T._dtype == np.uint8
+        assert T._offset == (0, 0, 0)
+        assert T._store is True
+        assert T._copy is False
+        assert T._need_resize is True
+        # assert T._data is data
+        assert len(T._pending_data) == 1
+
+    # Non contiguous data
+    # ---------------------------------
+    def test_init_non_contiguous_data(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        with use_log_level('warning', record=True, print_msg=False) as l:
+            T = Texture(data=data[::2, ::2])
+        assert len(l) == 1
+        assert T._shape == (5, 5, 1)
+        assert T._dtype == np.uint8
+        assert T._offset == (0, 0, 0)
+        assert T._store is True
+        assert T._copy is True
+        assert T._need_resize is True
+        assert T._pending_data
+        assert T._data is not data
+        assert len(T._pending_data) == 1
+
+    # Dtype and shape
+    # ---------------------------------
+    def test_init_dtype_shape(self):
+        T = Texture(shape=(10, 10), dtype=np.uint8)
+        assert T._shape == (10, 10, 1)
+        assert T._dtype == np.uint8
+        assert T._offset == (0, 0, 0)
+        assert T._store is True
+        assert T._copy is False
+        assert T._need_resize is True
+        assert not T._pending_data
+        assert T._data is not None
+        assert T._data.shape == (10, 10, 1)
+        assert T._data.dtype == np.uint8
+        assert len(T._pending_data) == 0
+        self.assertRaises(ValueError, Texture, shape=(10, 10), dtype=np.bool)
+        self.assertRaises(ValueError, Texture, shape=(10, 10),
+                          data=np.zeros((10, 10), np.float32))
+
+    # Dtype only
+    # ---------------------------------
+    def test_init_dtype(self):
+        self.assertRaises(ValueError, Texture, dtype=np.uint8)
+
+    # Data and dtype: dtype prevails
+    # ---------------------------------
+    def test_init_data_dtype(self):
+        data = np.zeros((10, 10, 1), dtype=np.uint8)
+        T = Texture(data=data, dtype=np.uint16)
+        assert T._shape == (10, 10, 1)
+        assert T._dtype == np.uint16
+        assert T._offset == (0, 0, 0)
+        assert T._store is True
+        assert T._copy is False
+        assert T._need_resize is True
+        assert T._data is not data
+        assert T._pending_data
+        assert len(T._pending_data) == 1
+
+    # Data, store but no copy
+    # ---------------------------------
+    def test_init_data_store(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        Texture(data=data, store=True)
+        # assert T._data is data
+
+    # Data, store and copy
+    # ---------------------------------
+    def test_init_data_store_copy(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data.copy(), store=True)
+        assert T._data is not data
+        assert T._data is not None
+
+    # Get a view of the whole texture
+    # ---------------------------------
+    def test_getitem_ellipsis(self):
+
+        data = np.zeros((10, 10, 3), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[...]
+        assert Z._base is T
+        # assert Z._data.base is T._data
+        assert Z._shape == (10, 10, 3)
+        assert Z._resizeable is False
+        assert len(Z._pending_data) == 0
+
+    # Get a view restrictions
+    # ---------------------------------
+    def test_view_restrictions(self):
+
+        data = np.zeros((10, 10, 3), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[...]
+        self.assertRaises(ValueError, Z.__getitem__, 1)
+        self.assertRaises(RuntimeError, Z.resize, (10, 10, 3))
+        self.assertRaises(ValueError, Z.resize, (10,))
+        T.resize(T.shape)  # noop
+        self.assertRaises(ValueError, Texture.interpolation.fset, Z, 'nearest')
+        assert Z.interpolation is T.interpolation
+        self.assertRaises(ValueError, Texture.wrapping.fset, Z, 'repeat')
+        assert Z.wrapping is T.wrapping
+
+    # Get a view using ellipsis at start
+    # ---------------------------------
+    def test_getitem_ellipsis_start(self):
+
+        data = np.zeros((10, 10, 3), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[..., 0]
+        assert Z._base is T
+        # assert Z._data.base is T._data
+        assert Z._shape == (10, 10, 1)
+        assert Z._resizeable is False
+        assert len(Z._pending_data) == 0
+
+    # Get a view using ellipsis at end
+    # ---------------------------------
+    def test_getitem_ellipsis_end(self):
+
+        data = np.zeros((10, 10, 3), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[0, ...]
+        assert Z._base is T
+        # assert Z._data.base is T._data
+        assert Z._shape == (1, 10, 3)
+        assert Z._resizeable is False
+        assert len(Z._pending_data) == 0
+
+    # Get a single item
+    # ---------------------------------
+    def test_getitem_single(self):
+
+        data = np.zeros((10, 10, 3), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[0, 0, 0]
+        assert Z._base is T
+        # assert Z._data.base is T._data
+        assert Z._shape == (1, 1, 1)
+        assert Z._resizeable is False
+        assert len(Z._pending_data) == 0
+
+    # Get a partial view
+    # ---------------------------------
+    def test_getitem_partial(self):
+
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[2:5, 2:5]
+        assert Z._base is T
+        # assert Z._data.base is T._data
+        assert Z._shape == (3, 3, 1)
+        assert Z._offset == (2, 2, 0)
+        assert Z._resizeable is False
+        assert len(Z._pending_data) == 0
+
+    # Get non contiguous view : forbidden
+    # ---------------------------------
+    def test_getitem_non_contiguous(self):
+
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        # with self.assertRaises(ValueError):
+        #    Z = T[::2, ::2]
+        #    print(Z)
+        s = slice(None, None, 2)
+        self.assertRaises(ValueError, T.__getitem__, (s, s))
+
+    # Set data with store
+    # ---------------------------------
+    def test_setitem_all(self):
+
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        T[...] = np.ones((10, 10, 1))
+        assert len(T._pending_data) == 1
+        assert np.allclose(data, np.ones((10, 10, 1)))
+
+    # Set data without store
+    # ---------------------------------
+    def test_setitem_all_no_store(self):
+
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data, store=False)
+        T[...] = np.ones((10, 10), np.uint8)
+        assert len(T._pending_data) == 1
+        assert np.allclose(data, np.zeros((10, 10)))
+
+    # Set a single data
+    # ---------------------------------
+    def test_setitem_single(self):
+
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        T[0, 0, 0] = 1
+        assert len(T._pending_data) == 2
+        assert data[0, 0] == 1, 1
+
+    # Set some data
+    # ---------------------------------
+    def test_setitem_partial(self):
+
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        T[5:, 5:] = 1
+        assert len(T._pending_data) == 2
+        assert np.allclose(data[5:, 5:], np.ones((5, 5)))
+
+    # Set non contiguous data
+    # ---------------------------------
+    def test_setitem_wrong(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        # with self.assertRaises(ValueError):
+        #    T[::2, ::2] = 1
+        s = slice(None, None, 2)
+        self.assertRaises(ValueError, T.__setitem__, (s, s), 1)
+
+    # Set via get (pending data on base)
+    # ---------------------------------
+    def test_getitem_setitem(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture(data=data)
+        Z = T[5:, 5:]
+        Z[...] = 1
+        assert len(Z._pending_data) == 0
+        assert len(T._pending_data) == 2
+        assert np.allclose(data[5:, 5:], np.ones((5, 5)))
+
+    # Set properties
+    def test_set_texture_properties(self):
+        T = Texture(shape=(10, 10), dtype=np.float32)
+        T.interpolation = 'linear'
+        assert T.interpolation == gl.GL_LINEAR
+        T.interpolation = ['linear'] * 2
+        assert T.interpolation == gl.GL_LINEAR
+        T.interpolation = ['linear', 'nearest']
+        assert T.interpolation == (gl.GL_LINEAR, gl.GL_NEAREST)
+        self.assertRaises(ValueError, Texture.interpolation.fset, T,
+                          ['linear'] * 3)
+        T.wrapping = 'clamp_to_edge'
+        assert T.wrapping == gl.GL_CLAMP_TO_EDGE
+
+
+# --------------------------------------------------------------- Texture2D ---
+class Texture2DTest(unittest.TestCase):
+    # Note: put many tests related to (re)sizing here, because Texture
+    # is not really aware of shape.
+
+    # Shape extension
+    # ---------------------------------
     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
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        assert T._shape == (10, 10, 1)
+
+    # Width & height
+    # ---------------------------------
+    def test_width_height(self):
+        data = np.zeros((10, 20), dtype=np.uint8)
+        T = Texture2D(data=data)
+        assert T.width == 20
+        assert T.height == 10
+
+    # Resize
+    # ---------------------------------
+    def test_resize(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        T.resize((5, 5))
+        assert T.shape == (5, 5, 1)
+        assert T._data.shape == (5, 5, 1)
+        assert T._need_resize is True
+        assert not T._pending_data
+        assert len(T._pending_data) == 0
+
+    # Resize with bad shape
+    # ---------------------------------
+    def test_resize_bad_shape(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        # with self.assertRaises(ValueError):
+        #    T.resize((5, 5, 5))
+        self.assertRaises(ValueError, T.resize, (5, 5, 5))
+
+    # Resize view (forbidden)
+    # ---------------------------------
+    def test_resize_view(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        # with self.assertRaises(RuntimeError):
+        #    T[...].resize((5, 5))
+        Z = T[...]
+        self.assertRaises(RuntimeError, Z.resize, (5, 5))
+
+    # Resize not resizeable
+    # ---------------------------------
+    def test_resize_unresizeable(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data, resizeable=False)
+        # with self.assertRaises(RuntimeError):
+        #    T.resize((5, 5))
+        self.assertRaises(RuntimeError, T.resize, (5, 5))
     
+    # Set oversized data (-> resize)
+    # ---------------------------------
+    def test_set_oversized_data(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        T.set_data(np.ones((20, 20), np.uint8))
+        assert T.shape == (20, 20, 1)
+        assert T._data.shape == (20, 20, 1)
+        assert len(T._pending_data) == 1
     
-    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)
+    # Set undersized data
+    # ---------------------------------
+    def test_set_undersized_data(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        T.set_data(np.ones((5, 5), np.uint8))
+        assert T.shape == (5, 5, 1)
+        assert len(T._pending_data) == 1
 
+    # Set misplaced data
+    # ---------------------------------
+    def test_set_misplaced_data(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        # with self.assertRaises(ValueError):
+        #    T.set_data(np.ones((5, 5)), offset=(8, 8))
+        self.assertRaises(ValueError, T.set_data,
+                          np.ones((5, 5)), offset=(8, 8))
+
+    # Set misshaped data
+    # ---------------------------------
+    def test_set_misshaped_data_2D(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        # with self.assertRaises(ValueError):
+        #    T.set_data(np.ones((10, 10)))
+        self.assertRaises(ValueError, T.set_data, np.ones((10,)))
+
+    # Set whole data (clear pending data)
+    # ---------------------------------
+    def test_set_whole_data(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        T.set_data(np.ones((10, 10), np.uint8))
+        assert T.shape == (10, 10, 1)
+        assert len(T._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)
-    
+    # Test view get invalidated when base is resized
+    # ----------------------------------------------
+    def test_invalid_views(self):
+        data = np.zeros((10, 10), dtype=np.uint8)
+        T = Texture2D(data=data)
+        Z = T[5:, 5:]
+        T.resize((5, 5))
+        assert Z._valid is False
     
-    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)
+    # Test set data with different shape
+    # ---------------------------------
+    def test_reset_data_shape(self):
+        shape1 = 10, 10
+        shape3 = 10, 10, 3
+        
+        # Init data (explicit shape)
+        data = np.zeros((10, 10, 1), dtype=np.uint8)
+        T = Texture2D(data=data)
+        assert T.shape == (10, 10, 1)
+        assert T._format == gl.GL_LUMINANCE
+        
+        # Set data to rgb
+        T.set_data(np.zeros(shape3, np.uint8))
+        assert T.shape == (10, 10, 3)
+        assert T._format == gl.GL_RGB
+        
+        # Set data to grayscale
+        T.set_data(np.zeros(shape1, np.uint8))
+        assert T.shape == (10, 10, 1)
+        assert T._format == gl.GL_LUMINANCE
+        
+        # Set size to rgb
+        T.resize(shape3)
+        assert T.shape == (10, 10, 3)
+        assert T._format == gl.GL_RGB
+        
+        # Set size to grayscale
+        T.resize(shape1)
+        assert T.shape == (10, 10, 1)
+        assert T._format == gl.GL_LUMINANCE
     
+    # Test set data with different shape
+    # ---------------------------------
+    def test_reset_data_type(self):
+        shape = 10, 10
+        T = Texture2D(data=np.zeros(shape, dtype=np.uint8))
+        assert T.dtype == np.uint8
+        assert T._gtype == gl.GL_UNSIGNED_BYTE
+
+        newdata = np.zeros(shape, dtype=np.float32)
+        self.assertRaises(ValueError, T.set_data, newdata)
 
+        newdata = np.zeros(shape, dtype=np.int32)
+        self.assertRaises(ValueError, T.set_data, newdata)
 
-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
 
+# --------------------------------------------------------------- Texture3D ---
+class Texture3DTest(unittest.TestCase):
+    # Note: put many tests related to (re)sizing here, because Texture
+    # is not really aware of shape.
 
+    @requires_pyopengl()
+    def __init__(self, *args, **kwds):
+        unittest.TestCase.__init__(self, *args, **kwds)
 
-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
+    # Shape extension
+    # ---------------------------------
+    def test_init(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        assert T._shape == (10, 10, 10, 1)
 
+    # Width & height
+    # ---------------------------------
+    def test_width_height_depth(self):
+        data = np.zeros((10, 20, 30), dtype=np.uint8)
+        T = Texture3D(data=data)
+        assert T.width == 30
+        assert T.height == 20
+        assert T.depth == 10
 
+    # Resize
+    # ---------------------------------
+    def test_resize(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        T.resize((5, 5, 5))
+        assert T.shape == (5, 5, 5, 1)
+        assert T._data.shape == (5, 5, 5, 1)
+        assert T._need_resize is True
+        assert not T._pending_data
+        assert len(T._pending_data) == 0
+
+    # Resize with bad shape
+    # ---------------------------------
+    def test_resize_bad_shape(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        # with self.assertRaises(ValueError):
+        #    T.resize((5, 5, 5, 5))
+        self.assertRaises(ValueError, T.resize, (5, 5, 5, 5))
+
+    # Resize not resizeable
+    # ---------------------------------
+    def test_resize_unresizeable(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data, resizeable=False)
+        # with self.assertRaises(RuntimeError):
+        #    T.resize((5, 5, 5))
+        self.assertRaises(RuntimeError, T.resize, (5, 5, 5))
+
+    # Set oversized data (-> resize)
+    # ---------------------------------
+    def test_set_oversized_data(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        T.set_data(np.ones((20, 20, 20), np.uint8))
+        assert T.shape == (20, 20, 20, 1)
+        assert T._data.shape == (20, 20, 20, 1)
+        assert len(T._pending_data) == 1
+
+    # Set undersized data
+    # ---------------------------------
+    def test_set_undersized_data(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        T.set_data(np.ones((5, 5, 5), np.uint8))
+        assert T.shape == (5, 5, 5, 1)
+        assert len(T._pending_data) == 1
+
+    # Set misplaced data
+    # ---------------------------------
+    def test_set_misplaced_data(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        # with self.assertRaises(ValueError):
+        #    T.set_data(np.ones((5, 5, 5)), offset=(8, 8, 8))
+        self.assertRaises(ValueError, T.set_data,
+                          np.ones((5, 5, 5)), offset=(8, 8, 8))
+
+    # Set misshaped data
+    # ---------------------------------
+    def test_set_misshaped_data_3D(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        # with self.assertRaises(ValueError):
+        #    T.set_data(np.ones((10, 10, 10)))
+        self.assertRaises(ValueError, T.set_data, np.ones((10,)))
+
+    # Set whole data (clear pending data)
+    # ---------------------------------
+    def test_set_whole_data(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        T.set_data(np.ones((10, 10, 10), np.uint8))
+        assert T.shape == (10, 10, 10, 1)
+        assert len(T._pending_data) == 1
+
+    # Test view get invalidated when base is resized
+    # ----------------------------------------------
+    def test_invalid_views(self):
+        data = np.zeros((10, 10, 10), dtype=np.uint8)
+        T = Texture3D(data=data)
+        Z = T[5:, 5:, 5:]
+        T.resize((5, 5, 5))
+        assert Z._valid is False
+
+    # Test set data with different shape
+    # ---------------------------------
+    def test_reset_data_shape(self):
+        shape1 = 10, 10, 10
+        shape3 = 10, 10, 10, 3
+        
+        # Init data (explicit shape)
+        data = np.zeros((10, 10, 10, 1), dtype=np.uint8)
+        T = Texture3D(data=data)
+        assert T.shape == (10, 10, 10, 1)
+        assert T._format == gl.GL_LUMINANCE
+        
+        # Set data to rgb
+        T.set_data(np.zeros(shape3, np.uint8))
+        assert T.shape == (10, 10, 10, 3)
+        assert T._format == gl.GL_RGB
+        
+        # Set data to grayscale
+        T.set_data(np.zeros(shape1, np.uint8))
+        assert T.shape == (10, 10, 10, 1)
+        assert T._format == gl.GL_LUMINANCE
+        
+        # Set size to rgb
+        T.resize(shape3)
+        assert T.shape == (10, 10, 10, 3)
+        assert T._format == gl.GL_RGB
+        
+        # Set size to grayscale
+        T.resize(shape1)
+        assert T.shape == (10, 10, 10, 1)
+        assert T._format == gl.GL_LUMINANCE
+
+    # Test set data with different shape
+    # ---------------------------------
+    def test_reset_data_type(self):
+        shape = 10, 10, 10
+        T = Texture3D(data=np.zeros(shape, dtype=np.uint8))
+        assert T.dtype == np.uint8
+        assert T._gtype == gl.GL_UNSIGNED_BYTE
+        
+        newdata = np.zeros(shape, dtype=np.float32)
+        self.assertRaises(ValueError, T.set_data, newdata)
+        
+        newdata = np.zeros(shape, dtype=np.int32)
+        self.assertRaises(ValueError, T.set_data, newdata)
+
+
+# -----------------------------------------------------------------------------
 if __name__ == "__main__":
     unittest.main()
-#     t = Texture2DTest()
-#     t.setUp()
-#     t.test_invalid_shape()
diff --git a/vispy/gloo/tests/test_use_gloo.py b/vispy/gloo/tests/test_use_gloo.py
new file mode 100644
index 0000000..853aa92
--- /dev/null
+++ b/vispy/gloo/tests/test_use_gloo.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
+# -----------------------------------------------------------------------------
+import numpy as np
+from numpy.testing import assert_allclose
+from nose.tools import assert_raises, assert_equal
+
+from vispy.app import Canvas
+from vispy.gloo import (Texture2D, Texture3D, Program, FrameBuffer,
+                        ColorBuffer, DepthBuffer, set_viewport, clear)
+from vispy.gloo.util import draw_texture, _screenshot
+from vispy.testing import requires_application, has_pyopengl
+
+
+ at requires_application()
+def test_use_textures():
+    """Test using textures and FBO"""
+    assert_raises(ValueError, Texture2D, np.zeros((2, 2, 3), np.float32),
+                  format='rgba')  # format and data size mismatch
+
+
+ at requires_application()
+def test_use_framebuffer():
+    """Test drawing to a framebuffer"""
+    shape = (100, 100)
+    data = np.random.rand(*shape).astype(np.float32)
+    orig_tex = Texture2D(data)
+    use_shape = shape + (3,)
+    fbo_tex = Texture2D(shape=use_shape, dtype=np.ubyte, format='rgb')
+    rbo = ColorBuffer(shape=shape)
+    fbo = FrameBuffer(color=fbo_tex)
+    with Canvas(size=(100, 100)) as c:
+        set_viewport((0, 0) + c.size)
+        with fbo:
+            draw_texture(orig_tex)
+        draw_texture(fbo_tex)
+        out_tex = _screenshot()[::-1, :, 0].astype(np.float32)
+        assert_raises(TypeError, FrameBuffer.color_buffer.fset, fbo, 1.)
+        assert_raises(TypeError, FrameBuffer.depth_buffer.fset, fbo, 1.)
+        assert_raises(TypeError, FrameBuffer.stencil_buffer.fset, fbo, 1.)
+        fbo.color_buffer = rbo
+        fbo.depth_buffer = DepthBuffer(shape)
+        fbo.stencil_buffer = None
+        print((fbo.color_buffer, fbo.depth_buffer, fbo.stencil_buffer))
+        clear(color='black')
+        with fbo:
+            clear(color='black')
+            draw_texture(orig_tex)
+            out_rbo = _screenshot()[:, :, 0].astype(np.float32)
+    assert_allclose(data * 255., out_tex, atol=1)
+    assert_allclose(data * 255., out_rbo, atol=1)
+
+
+ at requires_application()
+def test_use_texture3D():
+    """Test using a 3D texture"""
+    vals = [0, 200, 100, 0, 255, 0, 100]
+    d, h, w = len(vals), 3, 5
+    data = np.zeros((d, h, w), np.float32)
+    if not has_pyopengl():
+        assert_raises(ImportError, Texture3D(data))
+        return
+
+    VERT_SHADER = """
+    attribute vec2 a_pos;
+    varying vec2 v_pos;
+
+    void main (void)
+    {
+        v_pos = a_pos;
+        gl_Position = vec4(a_pos, 0., 1.);
+    }
+    """
+
+    FRAG_SHADER = """
+    uniform sampler3D u_texture;
+    varying vec2 v_pos;
+    uniform float i;
+    void main()
+    {
+        gl_FragColor = texture3D(u_texture,
+                                 vec3((v_pos.y+1.)/2., (v_pos.x+1.)/2., i));
+        gl_FragColor.a = 1.;
+    }
+    """
+    # populate the depth "slices" with different gray colors in the bottom left
+    for ii, val in enumerate(vals):
+        data[ii, :2, :3] = val / 255.
+    program = Program(VERT_SHADER, FRAG_SHADER)
+    program['a_pos'] = [[-1., -1.], [1., -1.], [-1., 1.], [1., 1.]]
+    tex = Texture3D(data)
+    assert_equal(tex.width, w)
+    assert_equal(tex.height, h)
+    assert_equal(tex.depth, d)
+    tex.interpolation = 'nearest'
+    program['u_texture'] = tex
+    with Canvas(size=(100, 100)):
+        for ii, val in enumerate(vals):
+            set_viewport(0, 0, w, h)
+            clear(color='black')
+            iii = (ii + 0.5) / float(d)
+            print(ii, iii)
+            program['i'] = iii
+            program.draw('triangle_strip')
+            out = _screenshot()[:, :, 0].astype(int)[::-1]
+            expected = np.zeros_like(out)
+            expected[:2, :3] = val
+            assert_allclose(out, expected, atol=1./255.)
diff --git a/vispy/gloo/tests/test_variable.py b/vispy/gloo/tests/test_variable.py
index 14ed025..bca74cd 100644
--- a/vispy/gloo/tests/test_variable.py
+++ b/vispy/gloo/tests/test_variable.py
@@ -1,127 +1,139 @@
+# -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# VisPy - Copyright (c) 2013, Vispy Development Team
-# All rights reserved.
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
 # -----------------------------------------------------------------------------
 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
+from vispy.gloo import gl
+from vispy.gloo.variable import Uniform, Variable, 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
-
+        variable = Variable(None, "A", gl.GL_FLOAT)
+        assert variable._handle == -1
+        assert variable.name == "A"
+        assert variable.data is None
+        assert variable.gtype == gl.GL_FLOAT
+        assert variable.enabled is True
 
     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(TypeError):
+        #    v = Variable(None, "A", gl.GL_INT_VEC2)
+        self.assertRaises(TypeError, Variable, None, "A", gl.GL_INT_VEC2)
+
+        # with self.assertRaises(TypeError):
+        #    v = Variable(None, "A", gl.GL_INT_VEC3)
+        self.assertRaises(TypeError, Variable, None, "A", gl.GL_INT_VEC3)
+
+        # with self.assertRaises(TypeError):
+        #    v = Variable(None, "A", gl.GL_INT_VEC4)
+        self.assertRaises(TypeError, Variable, None, "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)
+        # with self.assertRaises(TypeError):
+        #    v = Variable(None, "A", gl.GL_BOOL_VEC2)
+        self.assertRaises(TypeError, Variable, None, "A", gl.GL_BOOL_VEC2)
 
+        # with self.assertRaises(TypeError):
+        #    v = Variable(None, "A", gl.GL_BOOL_VEC3)
+        self.assertRaises(TypeError, Variable, None, "A", gl.GL_BOOL_VEC3)
+
+        # with self.assertRaises(TypeError):
+        #    v = Variable(None, "A", gl.GL_BOOL_VEC4)
+        self.assertRaises(TypeError, Variable, None, "A", gl.GL_BOOL_VEC4)
 
 
 # -----------------------------------------------------------------------------
 class UniformTest(unittest.TestCase):
 
     def test_init(self):
-        uniform = Uniform("A", gl.GL_FLOAT)
-        assert uniform.texture_unit == -1
+        uniform = Uniform(None, "A", gl.GL_FLOAT)
+        assert uniform._unit == -1
 
     def test_float(self):
-        uniform = Uniform("A", gl.GL_FLOAT)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 1
+        uniform = Uniform(None, "A", gl.GL_FLOAT)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 1
 
     def test_vec2(self):
-        uniform = Uniform("A", gl.GL_FLOAT_VEC2)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 2
+        uniform = Uniform(None, "A", gl.GL_FLOAT_VEC2)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 2
 
     def test_vec3(self):
-        uniform = Uniform("A", gl.GL_FLOAT_VEC2)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 2
+        uniform = Uniform(None, "A", gl.GL_FLOAT_VEC2)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 2
 
     def test_vec4(self):
-        uniform = Uniform("A", gl.GL_FLOAT_VEC2)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 2
+        uniform = Uniform(None, "A", gl.GL_FLOAT_VEC2)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 2
 
     def test_int(self):
-        uniform = Uniform("A", gl.GL_INT)
-        assert uniform.dtype == np.int32
-        assert uniform.size == 1
+        uniform = Uniform(None, "A", gl.GL_INT)
+        assert uniform.data.dtype == np.int32
+        assert uniform.data.size == 1
 
     def test_mat2(self):
-        uniform = Uniform("A", gl.GL_FLOAT_MAT2)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 4
+        uniform = Uniform(None, "A", gl.GL_FLOAT_MAT2)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 4
 
     def test_mat3(self):
-        uniform = Uniform("A", gl.GL_FLOAT_MAT3)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 9
+        uniform = Uniform(None, "A", gl.GL_FLOAT_MAT3)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 9
 
     def test_mat4(self):
-        uniform = Uniform("A", gl.GL_FLOAT_MAT4)
-        assert uniform.dtype == np.float32
-        assert uniform.size == 16
+        uniform = Uniform(None, "A", gl.GL_FLOAT_MAT4)
+        assert uniform.data.dtype == np.float32
+        assert uniform.data.size == 16
 
     def test_set(self):
-        uniform = Uniform("A", gl.GL_FLOAT_VEC4)
+        uniform = Uniform(None, "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()
+        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)
+        uniform = Uniform(None, "A", gl.GL_FLOAT_VEC4)
 
-        with self.assertRaises(ValueError):
-            uniform.set_data([1,2])
+        # with self.assertRaises(ValueError):
+        #    uniform.set_data([1, 2])
+        self.assertRaises(ValueError, uniform.set_data, [1, 2])
 
-        with self.assertRaises(ValueError):
-            uniform.set_data([1,2,3,4,5])
+        # with self.assertRaises(ValueError):
+        #    uniform.set_data([1, 2, 3, 4, 5])
+        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
+        attribute = Attribute(None, "A", gl.GL_FLOAT)
+        assert attribute.size == 0
 
     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 = Attribute(None, "A", gl.GL_FLOAT_VEC4)
 
         attribute.set_data(1)
         assert type(attribute.data) is np.ndarray
 
+#    @unittest.expectedFailure
+#    def test_set_generic_2(self):
+#        attribute = Attribute(None, "A", gl.GL_FLOAT_VEC4)
+#        attribute.set_data([1, 2, 3, 4])
+#        assert type(attribute.data) is np.ndarray
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/vispy/gloo/tests/test_wrappers.py b/vispy/gloo/tests/test_wrappers.py
new file mode 100644
index 0000000..8eaa186
--- /dev/null
+++ b/vispy/gloo/tests/test_wrappers.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Nicolas P. Rougier. All rights reserved.
+# Distributed under the terms of the new BSD License.
+# -----------------------------------------------------------------------------
+import numpy as np
+from numpy.testing import assert_array_equal
+from nose.tools import assert_true, assert_equal, assert_raises
+
+from vispy import gloo
+from vispy.gloo import gl
+from vispy.app import Canvas
+from vispy.testing import requires_application
+from vispy.gloo import read_pixels
+
+
+ at requires_application()
+def test_wrappers():
+    """Test gloo wrappers"""
+    with Canvas():
+        gl.use_gl('desktop debug')
+        # check presets
+        assert_raises(ValueError, gloo.set_state, preset='foo')
+        for state in gloo.get_state_presets().keys():
+            gloo.set_state(state)
+        assert_raises(ValueError, gloo.set_blend_color, (0., 0.))  # bad color
+        assert_raises(TypeError, gloo.set_hint, 1, 2)  # need strs
+        assert_raises(TypeError, gloo.get_parameter, 1)  # need str
+        # this doesn't exist in ES 2.0 namespace
+        assert_raises(ValueError, gloo.set_hint, 'fog_hint', 'nicest')
+        # test bad enum
+        assert_raises(RuntimeError, gloo.set_line_width, -1)
+
+        # check read_pixels
+        assert_true(isinstance(gloo.read_pixels(), np.ndarray))
+        assert_true(isinstance(gloo.read_pixels((0, 0, 1, 1)), np.ndarray))
+        assert_raises(ValueError, gloo.read_pixels, (0, 0, 1))  # bad port
+
+        # now let's (indirectly) check our set_* functions
+        viewport = (0, 0, 1, 1)
+        blend_color = (0., 0., 0.)
+        _funs = dict(viewport=viewport,  # checked
+                     hint=('generate_mipmap_hint', 'nicest'),
+                     depth_range=(1., 2.),
+                     front_face='cw',  # checked
+                     cull_face='front',
+                     line_width=1.,
+                     polygon_offset=(1., 1.),
+                     blend_func=('zero', 'one'),
+                     blend_color=blend_color,
+                     blend_equation='func_add',
+                     scissor=(0, 0, 1, 1),
+                     stencil_func=('never', 1, 2, 'back'),
+                     stencil_mask=4,
+                     stencil_op=('zero', 'zero', 'zero', 'back'),
+                     depth_func='greater',
+                     depth_mask=True,
+                     color_mask=(True, True, True, True),
+                     sample_coverage=(0.5, True))
+        gloo.set_state(**_funs)
+        gloo.clear((1., 1., 1., 1.), 0.5, 1)
+        gloo.flush()
+        gloo.finish()
+        # check some results
+        assert_array_equal(gloo.get_parameter('viewport'), viewport)
+        assert_equal(gloo.get_parameter('front_face'), gl.GL_CW)
+        assert_equal(gloo.get_parameter('blend_color'), blend_color + (1,))
+
+
+ at requires_application()
+def test_read_pixels():
+    """Test read_pixels to ensure that the image is not flipped"""
+    # Create vertices
+    vPosition = np.array([[-1, 1], [0, 1],  # For drawing a square to top left
+                          [-1, 0], [0, 0]], np.float32)
+
+    VERT_SHADER = """ // simple vertex shader
+    attribute vec2 a_position;
+    void main (void) {
+        gl_Position = vec4(a_position, 0., 1.0);
+    }
+    """
+
+    FRAG_SHADER = """ // simple fragment shader
+    void main()
+    {
+        gl_FragColor = vec4(1,1,1,1);
+    }
+    """
+
+    with Canvas() as c:
+        c._program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        c._program['a_position'] = gloo.VertexBuffer(vPosition)
+        gloo.set_clear_color((0, 0, 0, 0))  # Black background
+        gloo.clear()
+        c._program.draw('triangle_strip')
+
+        # Check if the return of read_pixels is the same as our drawing
+        img = read_pixels()
+        top_left = sum(img[0][0])
+        assert_true(top_left > 0)  # Should be > 0 (255*4)
+        # Sum of the pixels in top right + bottom left + bottom right corners
+        corners = sum(img[0][-1] + img[-1][0] + img[-1][-1])
+        assert_true(corners == 0)  # Should be all 0
+        gloo.flush()
+        gloo.finish()
diff --git a/vispy/gloo/texture.py b/vispy/gloo/texture.py
index 59a26c7..2fcc08c 100644
--- a/vispy/gloo/texture.py
+++ b/vispy/gloo/texture.py
@@ -1,724 +1,890 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+import numpy as np
 
-""" 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 . import gl
+from .globject import GLObject
+from .wrappers import _check_conversion
+from ..util import logger
 
 
-from __future__ import print_function, division, absolute_import
+GL_SAMPLER_3D = 35679
 
-import sys
-import numpy as np
 
-from vispy.util.six import string_types
-from . import gl
-from . import GLObject, ext_available, convert_to_enum
+def _check_pyopengl_3D():
+    """Helper to ensure users have OpenGL for 3D texture support (for now)"""
+    try:
+        import OpenGL.GL as _gl
+    except ImportError:
+        raise ImportError('PyOpenGL is required for 3D texture support')
+    return _gl
 
 
+def glTexImage3D(target, level, internalformat, format, type, pixels):
+    # Import from PyOpenGL
+    _gl = _check_pyopengl_3D()
+    border = 0
+    assert isinstance(pixels, (tuple, list))  # the only way we use this now
+    depth, height, width = pixels
+    _gl.glTexImage3D(target, level, internalformat,
+                     width, height, depth, border, format, type, None)
 
 
+def glTexSubImage3D(target, level, xoffset, yoffset, zoffset,
+                    format, type, pixels):
+    # Import from PyOpenGL
+    _gl = _check_pyopengl_3D()
+    depth, height, width = pixels.shape[:3]
+    _gl.glTexSubImage3D(target, level, xoffset, yoffset, zoffset,
+                        width, height, depth, format, type, pixels)
 
-class TextureError(RuntimeError):
-    """ Raised when something goes wrong that depens on state that was set 
-    earlier (due to deferred loading).
-    """
-    pass
 
+def _check_value(value, valid_dict):
+    """Helper for checking interpolation and wrapping"""
+    if not isinstance(value, (tuple, list)):
+        value = [value] * 2
+    if len(value) != 2:
+        raise ValueError('value must be a single value, or a 2-element list')
+    return tuple(_check_conversion(v, valid_dict) for v in value)
 
 
-class TextureLevel(object):
-    """ Minimal class to hold together some values that together
-    represent one texture level.
+# ----------------------------------------------------------- Texture class ---
+class BaseTexture(GLObject):
     """
-    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. 
-    
+    A Texture is used to represent a topological set of scalar values.
+
+    Parameters
+    ----------
+
+    target : GLEnum
+        gl.GL_TEXTURE2D
+        gl.GL_TEXTURE_CUBE_MAP
+    data : ndarray
+        Texture data (optional)
+    shape : tuple of integers
+        Texture shape (optional)
+    dtype : dtype
+        Texture data type (optional)
+    base : Texture
+        Base texture of this texture
+    offset : tuple of integers
+        Offset of this texture relative to base texture
+    store : bool
+        Specify whether this object stores a reference to the data,
+        allowing the data to be updated regardless of striding. Note
+        that modifying the data after passing it here might result in
+        undesired behavior, unless a copy is given. Default True.
+    resizeable : bool
+        Indicates whether texture can be resized
+    format : str | ENUM
+        The format of the texture: 'luminance', 'alpha', 'luminance_alpha',
+        'rgb', or 'rgba' (or ENUMs GL_LUMINANCE, ALPHA, GL_LUMINANCE_ALPHA,
+        or GL_RGB, GL_RGBA). If not given the format is chosen automatically
+        based on the number of channels. When the data has one channel,
+        'luminance' is assumed.
     """
-    
-    # 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):
+    _ndim = 2
+
+    _formats = {
+        1: gl.GL_LUMINANCE,  # or ALPHA,
+        2: gl.GL_LUMINANCE_ALPHA,
+        3: gl.GL_RGB,
+        4: gl.GL_RGBA
+    }
+
+    _inv_formats = {
+        gl.GL_LUMINANCE: 1,
+        gl.GL_ALPHA: 1,
+        gl.GL_LUMINANCE_ALPHA: 2,
+        gl.GL_RGB: 3,
+        gl.GL_RGBA: 4
+    }
+
+    _types = {
+        np.dtype(np.int8): gl.GL_BYTE,
+        np.dtype(np.uint8): gl.GL_UNSIGNED_BYTE,
+        np.dtype(np.int16): gl.GL_SHORT,
+        np.dtype(np.uint16): gl.GL_UNSIGNED_SHORT,
+        np.dtype(np.int32): gl.GL_INT,
+        np.dtype(np.uint32): gl.GL_UNSIGNED_INT,
+        # np.dtype(np.float16) : gl.GL_HALF_FLOAT,
+        np.dtype(np.float32): gl.GL_FLOAT,
+        # np.dtype(np.float64) : gl.GL_DOUBLE
+    }
+
+    def __init__(self, data=None, shape=None, dtype=None, base=None,
+                 target=None, offset=None, store=True, resizeable=True,
+                 format=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._data = None
+        self._base = base
+        self._store = store
+        self._copy = False  # flag to indicate that a copy is made
         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)
+        self._offset = offset
+        self._pending_data = []
+        self._resizeable = resizeable
+        self._valid = True
+        self._views = []
+
+        # Extra stages that are handled in _activate()
+        self._need_resize = False
+        self._need_parameterization = True
+        if base is None:
+            self.interpolation = 'nearest'
+            self.wrapping = 'clamp_to_edge'
+
+        # Do we have data to build texture upon ?
+        if data is not None:
+            self._need_resize = True
+            # Handle dtype
+            if dtype is not None:
+                data = np.array(data, dtype=dtype, copy=False)
+            else:
+                data = np.array(data, copy=False)
+            self._dtype = data.dtype
+            # Handle shape
+            data = self._normalize_shape(data)
+            if shape is not None:
+                raise ValueError('Texture needs data or shape, nor both.')
+            self._shape = data.shape
+            # Handle storage
+            if self._store:
+                if not data.flags["C_CONTIGUOUS"]:
+                    logger.warning("Copying discontiguous data as CPU storage")
+                    self._copy = True
+                    data = data.copy()
+                self._data = data
+            # Set data
+            self.set_data(data, copy=False)
+        elif dtype is not None:
+            if shape is not None:
+                self._need_resize = True
+            shape = shape or ()
+            self._shape = self._normalize_shape(shape)
+            self._dtype = dtype
+            if self._store:
+                self._data = np.zeros(self._shape, dtype=self._dtype)
         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)
+            raise ValueError("Either data or dtype must be given")
+
+        if offset is None:
+            self._offset = (0,) * len(self._shape)
         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
+            self._offset = offset
+
+        # Check dtype
+        if hasattr(self._dtype, 'fields') and self._dtype.fields:
+            raise ValueError("Texture dtype cannot be structured")
+
+        self._gtype = BaseTexture._types.get(np.dtype(self.dtype), None)
+        if self._gtype is None:
+            raise ValueError("Type not allowed for texture")
+
+        # Get and check format
+        valid_dict = {'luminance': gl.GL_LUMINANCE,
+                      'alpha': gl.GL_ALPHA,
+                      'luminance_alpha': gl.GL_LUMINANCE_ALPHA,
+                      'rgb': gl.GL_RGB,
+                      'rgba': gl.GL_RGBA}
+        counts = BaseTexture._inv_formats
         if format is None:
-            format = get_formats(shape, self._target)[0]
+            if len(self.shape) == 0:
+                raise ValueError('format must be provided if data and shape '
+                                 'are both None')
+            format = BaseTexture._formats.get(self.shape[-1], None)
+            if format is None:
+                raise ValueError("Cannot convert data to texture")
+            self._format = format
         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.
-        
+            # check to make sure it's a valid entry
+            out_format = _check_conversion(format, valid_dict)
+            # check to make sure that our shape does not conflict with the type
+            if len(self.shape) > 0 and self.shape[-1] != counts[out_format]:
+                raise ValueError('format %s size %s mismatch with input shape '
+                                 '%s' % (format, counts[out_format],
+                                         self.shape[-1]))
+            self._format = out_format
+
+    def _normalize_shape(self, data_or_shape):
+        # Get data and shape from input
+        if isinstance(data_or_shape, np.ndarray):
+            data = data_or_shape
+            shape = data.shape
+        else:
+            assert isinstance(data_or_shape, tuple)
+            data = None
+            shape = data_or_shape
+        # Check and correct
+        if shape:
+            if len(shape) < self._ndim:
+                raise ValueError("Too few dimensions for texture")
+            elif len(shape) > self._ndim + 1:
+                raise ValueError("Too many dimensions for texture")
+            elif len(shape) == self._ndim:
+                shape = shape + (1,)
+            else:  # if len(shape) == self._ndim + 1:
+                if shape[-1] > 4:
+                    raise ValueError("Too many channels for texture")
+        # Return
+        return data.reshape(shape) if data is not None else shape
+
+    @property
+    def shape(self):
+        """ Texture shape """
+        return self._shape
+
+    @property
+    def offset(self):
+        """ Texture offset """
+        return self._offset
+
+    @property
+    def dtype(self):
+        """ Texture data type """
+        return self._dtype
+
+    @property
+    def base(self):
+        """ Texture base if this texture is a view on another texture """
+        return self._base
+
+    @property
+    def data(self):
+        """ Texture CPU storage """
+        return self._data
+
+    @property
+    def wrapping(self):
+        """ Texture wrapping mode """
+        if self.base is not None:
+            return self.base.wrapping
+        value = self._wrapping
+        return value[0] if value[0] == value[1] else value
+
+    @wrapping.setter
+    def wrapping(self, value):
+        """ Texture wrapping mode """
+        if self.base is not None:
+            raise ValueError("Cannot set wrapping on texture view")
+        valid_dict = {'repeat': gl.GL_REPEAT,
+                      'clamp_to_edge': gl.GL_CLAMP_TO_EDGE,
+                      'mirrored_repeat': gl.GL_MIRRORED_REPEAT}
+        self._wrapping = _check_value(value, valid_dict)
+        self._need_parameterization = True
+
+    @property
+    def interpolation(self):
+        """ Texture interpolation for minification and magnification. """
+        if self.base is not None:
+            return self.base.interpolation
+        value = self._interpolation
+        return value[0] if value[0] == value[1] else value
+
+    @interpolation.setter
+    def interpolation(self, value):
+        """ Texture interpolation for minication and magnification. """
+        if self.base is not None:
+            raise ValueError("Cannot set interpolation on texture view")
+        valid_dict = {'nearest': gl.GL_NEAREST,
+                      'linear': gl.GL_LINEAR}
+        self._interpolation = _check_value(value, valid_dict)
+        self._need_parameterization = True
+
+    def resize(self, shape):
+        """ Resize the texture (deferred operation)
+
         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. 
-        
+
+        shape : tuple of integers
+            New texture shape
+
+        Notes
+        -----
+        This clears any pending operations.
         """
-        
-        # 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]
+        shape = self._normalize_shape(shape)
+
+        if not self._resizeable:
+            raise RuntimeError("Texture is not resizeable")
+
+        if self._base is not None:
+            raise RuntimeError("Texture view is not resizeable")
+
+        if len(shape) != len(self.shape):
+            raise ValueError("New shape has wrong number of dimensions")
+
+        if shape == self.shape:
+            return
+
+        # Reset format if size of last dimension differs
+        if shape[-1] != self.shape[-1]:
+            format = BaseTexture._formats.get(shape[-1], None)
+            if format is None:
+                raise ValueError("Cannot determine texture format from shape")
+            self._format = format
+
+        # Invalidate any view on this texture
+        for view in self._views:
+            view._valid = False
+        self._views = []
+
+        self._pending_data = []
+        self._need_resize = True
+        self._shape = shape
+        if self._data is not None and self._store:
+            self._data = np.resize(self._data, self._shape)
         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.
-        
+            self._data = None
+
+    def set_data(self, data, offset=None, copy=False):
+        """
+        Set data (deferred operation)
+
         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. 
-        
+
+        data : ndarray
+            Data to be uploaded
+        offset: int or tuple of ints
+            Offset in texture where to start copying data
+        copy: bool
+            Since the operation is deferred, data may change before
+            data is actually uploaded to GPU memory.
+            Asking explicitly for a copy will prevent this behavior.
+
+        Notes
+        -----
+        This operation implicitely resizes the texture to the shape of the data
+        if given offset is None.
         """
-        
-        # 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.')
+        if self.base is not None and not self._valid:
+            raise ValueError("This texture view has been invalidated")
+
+        if self.base is not None:
+            self.base.set_data(data, offset=self.offset, copy=copy)
+            return
+
+        # Force using the same data type. We could probably allow it,
+        # but with the views and data storage, this is rather complex.
+        if data.dtype != self.dtype:
+            raise ValueError('Cannot set texture data with another dtype.')
+
+        # Copy if needed, check/normalize shape
+        data = np.array(data, copy=copy)
+        data = self._normalize_shape(data)
+
+        # Check data has the right shape
+        # if len(data.shape) != len(self.shape):
+        #  raise ValueError("Data has wrong shape")
+
+        # Check if resize needed
+        if offset is None:
+            if data.shape != self.shape:
+                self.resize(data.shape)
+
+        if offset is None or offset == (0,) * len(self.shape):
+            if data.shape == self.shape:
+                self._pending_data = []
+
+            # Convert offset to something usable
+            offset = (0,) * len(self.shape)
+
+        # Check if data fits
+        for i in range(len(data.shape)):
+            if offset[i] + data.shape[i] > self.shape[i]:
+                raise ValueError("Data is too large")
+
+        if self._store:
+            pass
+            # todo: @nico should we not update self._data?
+            # but we need to keep the offset into account.
+
+        self._pending_data.append((data, offset))
+
+    def __getitem__(self, key):
+        """ x.__getitem__(y) <==> x[y] """
+        if self.base is not None:
+            raise ValueError("Can only access data from a base texture")
+
+        # Make sure key is a tuple
+        if isinstance(key, (int, slice)) or key == Ellipsis:
+            key = (key,)
+
+        # Default is to access the whole texture
+        shape = self.shape
+        slices = [slice(0, shape[i]) for i in range(len(shape))]
+
+        # Check last key/Ellipsis to decide on the order
+        keys = key[::+1]
+        dims = range(0, len(key))
+        if key[0] == Ellipsis:
+            keys = key[::-1]
+            dims = range(len(self.shape) - 1,
+                         len(self.shape) - 1 - len(keys), -1)
+
+        # Find exact range for each key
+        for k, dim in zip(keys, dims):
+            size = self.shape[dim]
+            if isinstance(k, int):
+                if k < 0:
+                    k += size
+                if k < 0 or k > size:
+                    raise IndexError("Texture assignment index out of range")
+                start, stop = k, k + 1
+                slices[dim] = slice(start, stop, 1)
+            elif isinstance(k, slice):
+                start, stop, step = k.indices(size)
+                if step != 1:
+                    raise ValueError("Cannot access non-contiguous data")
+                if stop < start:
+                    start, stop = stop, start
+                slices[dim] = slice(start, stop, step)
+            elif k == Ellipsis:
+                pass
+            else:
+                raise TypeError("Texture indices must be integers")
+
+        offset = tuple([s.start for s in slices])
+        shape = tuple([s.stop - s.start for s in slices])
+        data = None
+        if self.data is not None:
+            data = self.data[slices]
+
+        T = self.__class__(dtype=self.dtype, shape=shape,
+                           base=self, offset=offset, resizeable=False)
+        T._data = data
+        self._views.append(T)
+        return T
+
+    def __setitem__(self, key, data):
+        """ x.__getitem__(y) <==> x[y] """
+        if self.base is not None and not self._valid:
+            raise ValueError("This texture view has been invalited")
+
+        # Make sure key is a tuple
+        if isinstance(key, (int, slice)) or key == Ellipsis:
+            key = (key,)
+
+        # Default is to access the whole texture
+        shape = self.shape
+        slices = [slice(0, shape[i]) for i in range(len(shape))]
+
+        # Check last key/Ellipsis to decide on the order
+        keys = key[::+1]
+        dims = range(0, len(key))
+        if key[0] == Ellipsis:
+            keys = key[::-1]
+            dims = range(len(self.shape) - 1,
+                         len(self.shape) - 1 - len(keys), -1)
+
+        # Find exact range for each key
+        for k, dim in zip(keys, dims):
+            size = self.shape[dim]
+            if isinstance(k, int):
+                if k < 0:
+                    k += size
+                if k < 0 or k > size:
+                    raise IndexError("Texture assignment index out of range")
+                start, stop = k, k + 1
+                slices[dim] = slice(start, stop, 1)
+            elif isinstance(k, slice):
+                start, stop, step = k.indices(size)
+                if step != 1:
+                    raise ValueError("Cannot access non-contiguous data")
+                if stop < start:
+                    start, stop = stop, start
+                slices[dim] = slice(start, stop, step)
+            elif k == Ellipsis:
+                pass
+            else:
+                raise TypeError("Texture indices must be integers")
+
+        offset = tuple([s.start for s in slices])
+        shape = tuple([s.stop - s.start for s in slices])
+        size = np.prod(shape) if len(shape) > 0 else 1
+
+        # We have CPU storage
+        if self.data is not None:
+            self.data[key] = data
+            data = self.data[key]
         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
-    
-    
+            # Make sure data is an array
+            if not isinstance(data, np.ndarray):
+                data = np.array(data, dtype=self.dtype, copy=False)
+            # Make sure data is big enough
+            if data.size != size:
+                data = np.resize(data, size).reshape(shape)
+
+        # Set data (deferred)
+        if self.base is None:
+            self.set_data(data=data, offset=offset, copy=False)
+        else:
+            offset = self.offset + offset
+            self.base.set_data(data=data, offset=offset, copy=False)
+
+    def _parameterize(self):
+        """ Paramaterize texture """
+        gl.glTexParameterf(self._target, gl.GL_TEXTURE_MIN_FILTER,
+                           self._interpolation[0])
+        gl.glTexParameterf(self._target, gl.GL_TEXTURE_MAG_FILTER,
+                           self._interpolation[1])
+        gl.glTexParameterf(self._target, gl.GL_TEXTURE_WRAP_S,
+                           self._wrapping[0])
+        gl.glTexParameterf(self._target, gl.GL_TEXTURE_WRAP_T,
+                           self._wrapping[1])
+
     def _create(self):
-        self._handle = gl.glGenTextures(1)
-    
-    
+        """ Create texture on GPU """
+        logger.debug("GPU: Creating texture")
+        self._handle = gl.glCreateTexture()
+
     def _delete(self):
-        gl.glDeleteTextures([self._handle])
-    
-    
+        """ Delete texture from GPU """
+        logger.debug("GPU: Deleting texture")
+        gl.glDeleteTexture(self._handle)
+
     def _activate(self):
-        gl.glBindTexture(self._target, self._handle)
-    
-    
+        """ Activate texture on GPU """
+        logger.debug("GPU: Activate texture")
+        gl.glBindTexture(self.target, self._handle)
+
+        # We let base texture to handle all operations
+        if self.base is not None:
+            return
+
+        # Resize if necessary
+        if self._need_resize:
+            self._resize()
+            self._need_resize = False
+
+        # Reparameterize if necessary
+        if self._need_parameterization:
+            self._parameterize()
+            self._need_parameterization = False
+
+        # Update pending data if necessary
+        if self._pending_data:
+            logger.debug("GPU: Updating texture (%d pending operation(s))" %
+                         len(self._pending_data))
+            self._update_data()
+
     def _deactivate(self):
+        """ Deactivate texture on GPU """
+        logger.debug("GPU: Deactivate texture")
         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
+
+    # Taken from pygly
     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
+
+        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]
+        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 __repr__(self):
+        return "<%s shape=%r dtype=%r format=%r target=%r at 0x%x>" % (
+            self.__class__.__name__,
+            self._shape, self._dtype, self._format, self._target,
+            id(self))
+
+
+# --------------------------------------------------------- Texture2D class ---
+class Texture2D(BaseTexture):
+    """ Two dimensional texture
+
+    Parameters
+    ----------
+
+    data : ndarray
+        Texture data (optional), shaped as HxW.
+    shape : tuple of integers
+        Texture shape (optional), with shape HxW.
+    dtype : dtype
+        Texture data type (optional)
+    store : bool
+        Specify whether this object stores a reference to the data,
+        allowing the data to be updated regardless of striding. Note
+        that modifying the data after passing it here might result in
+        undesired behavior, unless a copy is given. Default True.
+    format : str | ENUM
+        The format of the texture: 'luminance', 'alpha', 'luminance_alpha',
+        'rgb', or 'rgba' (or ENUMs GL_LUMINANCE, ALPHA, GL_LUMINANCE_ALPHA,
+        or GL_RGB, GL_RGBA). If not given the format is chosen automatically
+        based on the number of channels. When the data has one channel,
+        'luminance' is assumed.
     """
-    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`.
+    _ndim = 2
+
+    def __init__(self, data=None, shape=None, dtype=None, store=True,
+                 format=None, **kwargs):
+
+        # We don't want these parameters to be seen from outside (because they
+        # are only used internally)
+        offset = kwargs.get("offset", None)
+        base = kwargs.get("base", None)
+        resizeable = kwargs.get("resizeable", True)
+        BaseTexture.__init__(self, data=data, shape=shape, dtype=dtype,
+                             base=base, resizeable=resizeable, store=store,
+                             target=gl.GL_TEXTURE_2D, offset=offset,
+                             format=format)
+
+    @property
+    def height(self):
+        """ Texture height """
+        return self._shape[0]
+
+    @property
+    def width(self):
+        """ Texture width """
+        return self._shape[1]
+
+    @property
+    def glsl_type(self):
+        """ GLSL declaration strings required for a variable to hold this data.
+        """
+        return 'uniform', 'sampler2D'
+
+    def _resize(self):
+        """ Texture resize on GPU """
+        logger.debug("GPU: Resizing texture(%sx%s)" %
+                     (self.width, self.height))
+        shape = self.height, self.width
+        gl.glTexImage2D(self.target, 0, self._format, self._format,
+                        self._gtype, shape)
+
+    def _update_data(self):
+        """ Texture update on GPU """
+        # Update data
+        while self._pending_data:
+            data, offset = self._pending_data.pop(0)
+            x = y = 0
+            if offset is not None:
+                y, x = offset[0], offset[1]
+            # Set alignment (width is nbytes_per_pixel * npixels_per_line)
+            alignment = self._get_alignment(data.shape[-2]*data.shape[-1])
+            if alignment != 4:
+                gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, alignment)
+            gl.glTexSubImage2D(self.target, 0, x, y, self._format,
+                               self._gtype, data)
+            if alignment != 4:
+                gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 4)
+
+
+# --------------------------------------------------------- Texture3D class ---
+class Texture3D(BaseTexture):
+    """ Three dimensional texture
+
+    Parameters
+    ----------
+    data : ndarray
+        Texture data (optional), shaped as DxHxW.
+    shape : tuple of integers
+        Texture shape (optional) DxHxW.
+    dtype : dtype
+        Texture data type (optional)
+    store : bool
+        Specify whether this object stores a reference to the data,
+        allowing the data to be updated regardless of striding. Note
+        that modifying the data after passing it here might result in
+        undesired behavior, unless a copy is given. Default True.
+    format : str | ENUM
+        The format of the texture: 'luminance', 'alpha', 'luminance_alpha',
+        'rgb', or 'rgba' (or ENUMs GL_LUMINANCE, ALPHA, GL_LUMINANCE_ALPHA,
+        or GL_RGB, GL_RGBA). If not given the format is chosen automatically
+        based on the number of channels. When the data has one channel,
+        'luminance' is assumed.
     """
-    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.
+    _ndim = 3
+
+    def __init__(self, data=None, shape=None, dtype=None, store=True,
+                 format=None, **kwargs):
+
+        # Import from PyOpenGL
+        _gl = _check_pyopengl_3D()
+
+        # We don't want these parameters to be seen from outside (because they
+        # are only used internally)
+        offset = kwargs.get("offset", None)
+        base = kwargs.get("base", None)
+        resizeable = kwargs.get("resizeable", True)
+        BaseTexture.__init__(self, data=data, shape=shape, dtype=dtype,
+                             base=base, resizeable=resizeable, store=store,
+                             target=_gl.GL_TEXTURE_3D, offset=offset,
+                             format=format)
+
+    @property
+    def width(self):
+        """ Texture width """
+        return self._shape[2]
+
+    @property
+    def height(self):
+        """ Texture height """
+        return self._shape[1]
+
+    @property
+    def depth(self):
+        """ Texture depth """
+        return self._shape[0]
+
+    @property
+    def glsl_type(self):
+        """ GLSL declaration strings required for a variable to hold this data.
+        """
+        return 'uniform', 'sampler3D'
+
+    def _resize(self):
+        """ Texture resize on GPU """
+        logger.debug("GPU: Resizing texture(%sx%sx%s)" %
+                     (self.depth, self.height, self.width))
+        glTexImage3D(self.target, 0, self._format, self._format,
+                     self._gtype, (self.depth, self.height, self.width))
+
+    def _update_data(self):
+        """ Texture update on GPU """
+        while self._pending_data:
+            data, offset = self._pending_data.pop(0)
+            z = y = x = 0
+            if offset is not None:
+                z, y, x = offset[0], offset[1], offset[2]
+            # Set alignment (width is nbytes_per_pixel * npixels_per_line)
+            alignment = self._get_alignment(data.shape[-3] *
+                                            data.shape[-2] * data.shape[-1])
+            if alignment != 4:
+                gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, alignment)
+            glTexSubImage3D(self.target, 0, x, y, z, self._format,
+                            self._gtype, data)
+            if alignment != 4:
+                gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 4)
+
+
+# ------------------------------------------------------ TextureAtlas class ---
+class TextureAtlas(Texture2D):
+    """Group multiple small data regions into a larger texture.
+
+    The algorithm is based on the article by Jukka Jylänki : "A Thousand Ways
+    to Pack the Bin - A Practical Approach to Two-Dimensional Rectangle Bin
+    Packing", February 27, 2010. More precisely, this is an implementation of
+    the Skyline Bottom-Left algorithm based on C++ sources provided by Jukka
+    Jylänki at: http://clb.demon.fi/files/RectangleBinPack/.
+
+    Parameters
+    ----------
+    shape : tuple of int
+        Texture width and height (optional).
+
+    Notes
+    -----
+    This creates a 2D texture that holds 1D float32 data.
+    An example of simple access:
+
+        >>> atlas = TextureAtlas()
+        >>> bounds = atlas.get_free_region(20, 30)
+        >>> atlas.set_region(bounds, np.random.rand(20, 30).T)
     """
-    # 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)
-
+    def __init__(self, shape=(1024, 1024)):
+        shape = np.array(shape, int)
+        assert shape.ndim == 1 and shape.size == 2
+        shape = tuple(2 ** (np.log2(shape) + 0.5).astype(int)) + (3,)
+        self._atlas_nodes = [(0, 0, shape[1])]
+        data = np.zeros(shape, np.float32)
+        super(TextureAtlas, self).__init__(data)
+        self.interpolation = 'linear'
+        self.wrapping = 'clamp_to_edge'
+
+    def get_free_region(self, width, height):
+        """Get a free region of given size and allocate it
 
-
-## 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
+        Parameters
+        ----------
+        width : int
+            Width of region to allocate
+        height : int
+            Height of region to allocate
+
+        Returns
+        -------
+        bounds : tuple | None
+            A newly allocated region as (x, y, w, h) or None
+            (if failed).
+        """
+        best_height = best_width = np.inf
+        best_index = -1
+        for i in range(len(self._atlas_nodes)):
+            y = self._fit(i, width, height)
+            if y >= 0:
+                node = self._atlas_nodes[i]
+                if (y+height < best_height or
+                        (y+height == best_height and node[2] < best_width)):
+                    best_height = y+height
+                    best_index = i
+                    best_width = node[2]
+                    region = node[0], y, width, height
+        if best_index == -1:
+            return None
+
+        node = region[0], region[1] + height, width
+        self._atlas_nodes.insert(best_index, node)
+        i = best_index+1
+        while i < len(self._atlas_nodes):
+            node = self._atlas_nodes[i]
+            prev_node = self._atlas_nodes[i-1]
+            if node[0] < prev_node[0]+prev_node[2]:
+                shrink = prev_node[0]+prev_node[2] - node[0]
+                x, y, w = self._atlas_nodes[i]
+                self._atlas_nodes[i] = x+shrink, y, w-shrink
+                if self._atlas_nodes[i][2] <= 0:
+                    del self._atlas_nodes[i]
+                    i -= 1
+                else:
+                    break
+            else:
+                break
+            i += 1
+
+        # Merge nodes
+        i = 0
+        while i < len(self._atlas_nodes)-1:
+            node = self._atlas_nodes[i]
+            next_node = self._atlas_nodes[i+1]
+            if node[1] == next_node[1]:
+                self._atlas_nodes[i] = node[0], node[1], node[2]+next_node[2]
+                del self._atlas_nodes[i+1]
+            else:
+                i += 1
+
+        return region
+
+    def _fit(self, index, width, height):
+        """Test if region (width, height) fit into self._atlas_nodes[index]"""
+        node = self._atlas_nodes[index]
+        x, y = node[0], node[1]
+        width_left = width
+        if x+width > self.shape[1]:
+            return -1
+        i = index
+        while width_left > 0:
+            node = self._atlas_nodes[i]
+            y = max(y, node[1])
+            if y+height > self.shape[0]:
+                return -1
+            width_left -= node[2]
+            i += 1
+        return y
diff --git a/vispy/gloo/util.py b/vispy/gloo/util.py
new file mode 100644
index 0000000..fa745fb
--- /dev/null
+++ b/vispy/gloo/util.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from .wrappers import read_pixels
+
+
+def _screenshot(viewport=None, alpha=True):
+    """ Take a screenshot using glReadPixels. Not sure where to put this
+    yet, so a private function for now. Used in make.py.
+
+    Parameters
+    ----------
+    viewport : array-like | None
+        4-element list of x, y, w, h parameters. If None (default),
+        the current GL viewport will be queried and used.
+    alpha : bool
+        If True (default), the returned array has 4 elements (RGBA).
+        Otherwise, it has 3 (RGB)
+
+    Returns
+    -------
+    pixels : array
+        3D array of pixels in np.uint8 format
+    """
+    # gl.glReadBuffer(gl.GL_BACK)  Not avaliable in ES 2.0
+    return read_pixels(viewport, alpha)
+
+
+KEYWORDS = set(['active', 'asm', 'cast', 'class', 'common', 'default',
+                'double', 'dvec2', 'dvec3', 'dvec4', 'enum', 'extern',
+                'external', 'filter', 'fixed', 'flat', 'fvec2', 'fvec3',
+                'fvec4', 'goto', 'half', 'hvec2', 'hvec3', 'hvec4', 'iimage1D',
+                'iimage1DArray', 'iimage2D', 'iimage2DArray', 'iimage3D',
+                'iimageBuffer', 'iimageCube', 'image1D', 'image1DArray',
+                'image1DArrayShadow', 'image1DShadow', 'image2D',
+                'image2DArray', 'image2DArrayShadow', 'image2DShadow',
+                'image3D', 'imageBuffer', 'imageCube', 'inline', 'input',
+                'interface', 'long', 'namespace', 'noinline', 'output',
+                'packed', 'partition', 'public', 'row_major', 'sampler1D',
+                'sampler1DShadow', 'sampler2DRect', 'sampler2DRectShadow',
+                'sampler2DShadow', 'sampler3D', 'sampler3DRect', 'short',
+                'sizeof', 'static', 'superp', 'switch', 'template', 'this',
+                'typedef', 'uimage1D', 'uimage1DArray', 'uimage2D',
+                'uimage2DArray', 'uimage3D', 'uimageBuffer', 'uimageCube',
+                'union', 'unsigned', 'using', 'volatile'])
+
+
+def check_variable(name):
+    """
+    Return None if *name* is expected to be a valid variable name in any GLSL
+    version. Otherwise, return a string describing the problem.
+    """
+    # Limit imposed by glGetActive* in pyopengl
+    if len(name) > 31:
+        return ("Variable names >31 characters may not function on some "
+                "systems.")
+
+    return check_identifier(name)
+
+
+def check_identifier(name):
+    if '__' in name:
+        return "Identifiers may not contain double-underscores."
+
+    if name[:3] == 'gl_' or name[:3] == 'GL_':
+        return "Identifiers may not begin with gl_ or GL_."
+
+    if name in KEYWORDS:
+        return "Identifier is a reserved keyword."
+
+
+vert_draw = """
+attribute vec2 a_position;
+attribute vec2 a_texcoord;
+varying vec2 v_uv;
+
+void main(void) {
+    v_uv = a_texcoord.xy;
+    gl_Position = vec4(a_position, 0, 1);
+}
+"""
+
+frag_draw = """
+uniform sampler2D u_texture;
+varying vec2 v_uv;
+
+void main(void) {
+    gl_FragColor = texture2D(u_texture, v_uv).rgba;
+}
+"""
+
+
+def draw_texture(tex):
+    """Draw a 2D texture to the current viewport
+
+    Parameters
+    ----------
+    tex : instance of Texture2D
+        The texture to draw.
+    """
+    from .program import Program
+    program = Program(vert_draw, frag_draw)
+    program['u_texture'] = tex
+    program['a_position'] = [[-1., -1.], [-1., 1.], [1., -1.], [1., 1.]]
+    program['a_texcoord'] = [[0., 1.], [0., 0.], [1., 1.], [1., 0.]]
+    program.draw('triangle_strip')
diff --git a/vispy/gloo/variable.py b/vispy/gloo/variable.py
index f94cba3..7431f15 100644
--- a/vispy/gloo/variable.py
+++ b/vispy/gloo/variable.py
@@ -1,458 +1,386 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
 # 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
+from .globject import GLObject
+from .buffer import VertexBuffer, DataBufferView
+from .texture import BaseTexture, Texture2D, Texture3D, GL_SAMPLER_3D
+from .framebuffer import RenderBuffer
+from ..util import logger
+from .util import check_variable
 
-# todo: support arrays of uniforms
 
+# ------------------------------------------------------------- gl_typeinfo ---
 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.
+    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_1D   : ( 1, gl.GL_UNSIGNED_INT, np.uint32),
+    gl.GL_SAMPLER_2D: (1, gl.GL_UNSIGNED_INT, np.uint32),
+    GL_SAMPLER_3D: (1, gl.GL_UNSIGNED_INT, np.uint32)
+}
+
+
+# ---------------------------------------------------------- Variable class ---
+class Variable(GLObject):
+    """ A variable is an interface between a program and some data 
+    
+    For internal use
+    
+    Parameters
+    ----------
+    
+    program : Program
+        The Program instance to which the data applies
+    name : str
+        The variable name
+    gtype : ENUM
+        The type of the variable: GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3,
+        GL_FLOAT_VEC4, GL_INT, GL_BOOL, GL_FLOAT_MAT2, GL_FLOAT_MAT3,
+        GL_FLOAT_MAT4, gl.GL_SAMPLER_2D, or GL_SAMPLER_3D
+    
     """
 
-    # NOTE: Variable, Uniform, Attribute are FRIENDS of Program,
-    # and Program manimpulates the private attributes of these objects.
-
-    # ---------------------------------
-    def __init__(self, name, gtype):
+    def __init__(self, program, 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")
-        
+        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_1D,
+                         gl.GL_SAMPLER_2D,
+                         GL_SAMPLER_3D]:
+            raise TypeError("Unknown variable type")
+
+        GLObject.__init__(self)
+
+        # Program this variable belongs to
+        self._program = program
+
         # Name of this variable in the program
         self._name = name
-        
-        # GL type and size (i.e. size of the vector)
+        check = check_variable(name)
+        if check:
+            logger.warning('Invalid variable name "%s". (%s)'
+                           % (name, check))
+
+        # Build dtype
+        size, _, base = gl_typeinfo[gtype]
+        self._dtype = (name, base, size)
+
+        # GL type
         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
-    
-    
+
+        # Whether this variable is actually being used by GLSL
+        self._enabled = True
+
     @property
     def name(self):
-        """ The name of the variable. """
+        """ Variable name """
+
         return self._name
-    
+
+    @property
+    def program(self):
+        """ Program this variable belongs to """
+
+        return self._program
+
     @property
     def gtype(self):
-        """ The type of the underlying variable (as a GL constant). """
+        """ 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). """
+        """ Equivalent dtype of the variable """
+
         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
+    def enabled(self):
+        """ Whether this variable is being used by the program """
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        """ Whether this variable is being used by the program """
+        self._enabled = bool(enabled)
 
-    
     @property
     def data(self):
-        """ The data for this variable (ndarray, VertexBuffer or Texture). """
+        """ CPU data """
         return self._data
 
-
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.name)
 
 
 # ----------------------------------------------------------- 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
-    
+    """ A Uniform represents a program uniform variable. 
     
+    See Variable.
+    """
+
+    # todo: store function names instead of GL proxy function (faster)
+    _ufunctions = {
+        gl.GL_FLOAT:        gl.proxy.glUniform1fv,
+        gl.GL_FLOAT_VEC2:   gl.proxy.glUniform2fv,
+        gl.GL_FLOAT_VEC3:   gl.proxy.glUniform3fv,
+        gl.GL_FLOAT_VEC4:   gl.proxy.glUniform4fv,
+        gl.GL_INT:          gl.proxy.glUniform1iv,
+        gl.GL_BOOL:         gl.proxy.glUniform1iv,
+        gl.GL_FLOAT_MAT2:   gl.proxy.glUniformMatrix2fv,
+        gl.GL_FLOAT_MAT3:   gl.proxy.glUniformMatrix3fv,
+        gl.GL_FLOAT_MAT4:   gl.proxy.glUniformMatrix4fv,
+        #        gl.GL_SAMPLER_1D:   gl.proxy.glUniform1i,
+        gl.GL_SAMPLER_2D:   gl.proxy.glUniform1i,
+        GL_SAMPLER_3D:   gl.proxy.glUniform1i,
+    }
+
+    def __init__(self, program, name, gtype):
+        """ Initialize the input into default state """
+
+        Variable.__init__(self, program, name, gtype)
+        size, _, dtype = gl_typeinfo[self._gtype]
+        self._data = np.zeros(size, dtype)
+        self._ufunction = Uniform._ufunctions[self._gtype]
+        self._unit = -1
+        self._need_update = False
+
     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):
+        """ Set data (no upload) """
+        if self._gtype == gl.GL_SAMPLER_2D:
+            if isinstance(data, Texture2D):
+                self._data = data
+            elif isinstance(self._data, Texture2D):
+                self._data.set_data(data)
+            elif isinstance(data, RenderBuffer):
                 self._data = data
             else:
-                raise ValueError('Expected a Texture for uniform %s.' % self.name)
+                # Automatic texture creation if required
+                data = np.array(data, copy=False)
+                if data.dtype in [np.float16, np.float32, np.float64]:
+                    self._data = Texture2D(data=data.astype(np.float32))
+                else:
+                    self._data = Texture2D(data=data.astype(np.uint8))
+        elif self._gtype == GL_SAMPLER_3D:
+            if isinstance(data, Texture3D):
+                self._data = data
+            elif isinstance(self._data, Texture3D):
+                self._data.set_data(data)
+            elif isinstance(data, RenderBuffer):
+                self._data = data
+            else:
+                # Automatic texture creation if required
+                data = np.array(data, copy=False)
+                if data.dtype in [np.float16, np.float32, np.float64]:
+                    self._data = Texture3D(data=data.astype(np.float32))
+                else:
+                    self._data = Texture3D(data=data.astype(np.uint8))
         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
-        
+            self._data[...] = np.array(data, copy=False).ravel()
+
+        self._need_update = True
+
+    def _activate(self):
+        # if self._gtype in (gl.GL_SAMPLER_1D, gl.GL_SAMPLER_2D):
+        if self._gtype in (gl.GL_SAMPLER_2D, GL_SAMPLER_3D):
+            logger.debug("GPU: Active texture is %d" % self._unit)
+            gl.glActiveTexture(gl.GL_TEXTURE0 + self._unit)
+            if isinstance(self._data, BaseTexture):
+                self._data.activate()
+
+        # Update if necessary. OpenGL stores uniform values at the Program
+        # object, so they only have to be set once.
+        if self._need_update:
+            self._update()
+            self._need_update = False
+
+    def _deactivate(self):
+        if self._gtype in (gl.GL_SAMPLER_2D, GL_SAMPLER_3D):
+            #gl.glActiveTexture(gl.GL_TEXTURE0 + self._unit)
+            if self.data is not None:
+                self.data.deactivate()
+
+    def _update(self):
+
         # Check active status (mandatory)
-        if self._loc is None:
-            raise VariableError("Uniform is not active")
+        if not self._enabled:
+            raise RuntimeError("Uniform %r is not active" % self.name)
+        if self._data is None:
+            raise RuntimeError("Uniform data not set for %r" % self.name)
         
-        #  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
+        if self._gtype in (gl.GL_FLOAT_MAT2,
+                           gl.GL_FLOAT_MAT3, gl.GL_FLOAT_MAT4):
             # OpenGL ES 2.0 does not support transpose
-            transpose = False 
-            self._ufunction(self._loc, 1, transpose, self._data)
-            self._dirty = False
-            
+            transpose = False
+            self._ufunction(self._handle, 1, transpose, self._data)
+
         # 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)
-            
+        # elif self._gtype in (gl.GL_SAMPLER_1D, gl.GL_SAMPLER_2D):
+        elif self._gtype in (gl.GL_SAMPLER_2D, GL_SAMPLER_3D):
+            # texture = self.data
+            # log("GPU: Active texture is %d" % self._unit)
+            # gl.glActiveTexture(gl.GL_TEXTURE0 + self._unit)
+            # gl.glBindTexture(texture.target, texture.handle)
+            gl.glUniform1i(self._handle, self._unit)
+
         # Regular uniform
         else:
-            if not self._dirty:
-                return
-            self._ufunction(self._loc, 1, self._data)
-        
-        # Mark as uploaded
-        self._dirty = False
-
-
+            self._ufunction(self._handle, 1, self._data)
 
+    def _create(self):
+        """ Create uniform on GPU (get handle) """
+        self._handle = gl.glGetUniformLocation(
+            self._program.handle, self._name)
+    
+    def _delete(self):
+        pass  # No need to delete variables; they are not really *objects*
 
 
 # --------------------------------------------------------- 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.
+    """ An Attribute represents a program attribute variable 
     
-    _afunctions = { 
-        gl.GL_FLOAT:        gl.glVertexAttrib1f,
-        gl.GL_FLOAT_VEC2:   gl.glVertexAttrib2f,
-        gl.GL_FLOAT_VEC3:   gl.glVertexAttrib3f,
-        gl.GL_FLOAT_VEC4:   gl.glVertexAttrib4f
+    See Variable.
+    """
+
+    _afunctions = {
+        gl.GL_FLOAT:      gl.proxy.glVertexAttrib1f,
+        gl.GL_FLOAT_VEC2: gl.proxy.glVertexAttrib2f,
+        gl.GL_FLOAT_VEC3: gl.proxy.glVertexAttrib3f,
+        gl.GL_FLOAT_VEC4: gl.proxy.glVertexAttrib4f
     }
 
+    def __init__(self, program, name, gtype):
+        """ Initialize the input into default state """
+
+        Variable.__init__(self, program, name, gtype)
+
+        # Number of elements this attribute links to (in the attached buffer)
+        self._size = 0
 
-    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. """
+        """ Set data (deferred operation) """
         
-        # No magic conversion to VertexBuffer here. If we want it, we 
-        # should do it in Program.__setitem__
+        isnumeric = isinstance(data, (float, int))
         
-        # 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.')
+        if (isinstance(data, VertexBuffer) or
+            (isinstance(data, DataBufferView) and 
+             isinstance(data.base, VertexBuffer))):
+            # New vertex buffer
+            self._data = data
+        elif isinstance(self._data, VertexBuffer):
+            # We already have a vertex buffer
+            self._data.set_data(data)
+        elif (isnumeric or (isinstance(data, (tuple, list)) and
+                            len(data) in (1, 2, 3, 4) and
+                            isinstance(data[0], (float, int)))):
+            # Data is a tuple with size <= 4, we assume this designates
+            # a generate vertex attribute.
             # Let numpy convert the data for us
-            self._data = np.array(data, dtype=dtype)
-            self._data.shape = self._data.size,
-            # Set generic and afunc
+            _, _, dtype = gl_typeinfo[self._gtype]
+            self._data = np.array(data).astype(dtype)
             self._generic = True
+            #self._need_update = 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. ')
+            return
         else:
-            raise ValueError('Wrong data for attribute.')
+            # For array-like, we need to build a proper VertexBuffer
+            # to be able to upload it later to GPU memory.
+            name, base, count = self.dtype
+            data = np.array(data, dtype=base, copy=False)
+            data = data.ravel().view([self.dtype])
+            # WARNING : transform data with the right type
+            # data = np.array(data,copy=False)
+            self._data = VertexBuffer(data)
         
-        # Mark variable as dirty
-        self._dirty = True
-    
+        self._generic = False
 
-    def upload(self, program):
+    def _activate(self):
+        # Update always, attributes are not stored at the Program object
+        self._update()
+    
+    def _deactivate(self):
+        if isinstance(self.data, VertexBuffer):
+            self.data.deactivate()
+    
+    def _update(self):
         """ 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
+
+        #logger.debug("GPU: Updating %s" % self.name)
         
         # 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
+        if not self._enabled:
+            raise RuntimeError("Attribute %r is not active" % self.name)
+        if self._data is None:
+            raise RuntimeError("Attribute data not set for %r" % self.name)
         
         # 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)
+            if self._handle >= 0:
+                gl.glDisableVertexAttribArray(self._handle)
+                self._afunction(self._handle, *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)
+        # Regular vertex buffer
+        elif self._handle >= 0:
             
-            # Disable any VBO
-            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
+            # Activate the VBO
+            self.data.activate()
             
-            # 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)
+            # Get relevant information from gl_typeinfo
+            size, gtype, dtype = gl_typeinfo[self._gtype]
+            stride = self.data.stride
+            offset = self.data.offset
+
+            gl.glEnableVertexAttribArray(self.handle)
+            gl.glVertexAttribPointer(
+                self.handle, size, gtype, gl.GL_FALSE, stride, offset)
+
+    def _create(self):
+        """ Create attribute on GPU (get handle) """
+        self._handle = gl.glGetAttribLocation(self._program.handle, self.name)
     
+    def _delete(self):
+        pass  # No need to delete variables; they are not really *objects*
+    
+    @property
+    def size(self):
+        """ Size of the underlying vertex buffer """
 
-# -----------------------------------------------------------------------------
-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)
-
+        if self._data is None:
+            return 0
+        return self._data.size
diff --git a/vispy/gloo/wrappers.py b/vispy/gloo/wrappers.py
new file mode 100644
index 0000000..9f04e9b
--- /dev/null
+++ b/vispy/gloo/wrappers.py
@@ -0,0 +1,674 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+import numpy as np
+from copy import deepcopy
+
+from . import gl
+from ..ext.six import string_types
+from ..color import Color
+
+
+__all__ = ('set_viewport', 'set_depth_range', 'set_front_face',
+           'set_cull_face', 'set_line_width', 'set_polygon_offset',
+           'clear', 'set_clear_color', 'set_clear_depth', 'set_clear_stencil',
+           'set_blend_func', 'set_blend_color', 'set_blend_equation',
+           'set_scissor', 'set_stencil_func', 'set_stencil_mask',
+           'set_stencil_op', 'set_depth_func', 'set_depth_mask',
+           'set_color_mask', 'set_sample_coverage',
+           'get_state_presets', 'set_state', 'finish', 'flush',
+           'get_parameter', 'read_pixels', 'set_hint',
+           'get_gl_configuration', 'check_error', '_check_valid')
+_setters = [s[4:] for s in __all__
+            if s.startswith('set_') and s != 'set_state']
+
+
+# Helpers that are needed for efficient wrapping
+
+def _check_valid(key, val, valid):
+    """Helper to check valid options"""
+    if val not in valid:
+        raise ValueError('%s must be one of %s, not "%s"'
+                         % (key, valid, val))
+
+
+def _gl_attr(x):
+    """Helper to return gl.GL_x enum"""
+    y = 'GL_' + x.upper()
+    z = getattr(gl, y, None)
+    if z is None:
+        raise ValueError('gl has no attribute corresponding to name %s (%s)'
+                         % (x, y))
+    return z
+
+
+def _gl_bool(x):
+    """Helper to convert to GL boolean"""
+    return gl.GL_TRUE if x else gl.GL_FALSE
+
+
+def _to_args(x):
+    """Convert to args representation"""
+    if not isinstance(x, (list, tuple, np.ndarray)):
+        x = [x]
+    return x
+
+
+def _check_conversion(key, valid_dict):
+    """Check for existence of key in dict, return value or raise error"""
+    if key not in valid_dict and key not in valid_dict.values():
+        # Only show users the nice string values
+        keys = [v for v in valid_dict.keys() if isinstance(key, string_types)]
+        raise ValueError('value must be one of %s, not %s' % (keys, key))
+    return valid_dict[key] if key in valid_dict else key
+
+
+###############################################################################
+# PRIMITIVE/VERTEX
+
+#
+# Viewport, DepthRangef, CullFace, FrontFace, LineWidth, PolygonOffset
+#
+
+def set_viewport(*args):
+    """Set the OpenGL viewport
+
+    This is a wrapper for gl.glViewport.
+
+    Parameters
+    ----------
+    x, y, w, h : int | tuple
+        X and Y coordinates, plus width and height. Can be passed in as
+        individual components, or as a single tuple with four values.
+    """
+    x, y, w, h = args[0] if len(args) == 1 else args
+    gl.glViewport(int(x), int(y), int(w), int(h))
+
+
+def set_depth_range(near=0., far=1.):
+    """Set depth values
+
+    Parameters
+    ----------
+    near : float
+        Near clipping plane.
+    far : float
+        Far clipping plane.
+    """
+    gl.glDepthRange(float(near), float(far))
+
+
+def set_front_face(mode='ccw'):
+    """Set which faces are front-facing
+
+    Parameters
+    ----------
+    mode : str
+        Can be 'cw' for clockwise or 'ccw' for counter-clockwise.
+    """
+    gl.glFrontFace(_gl_attr(mode))
+
+
+def set_cull_face(mode='back'):
+    """Set front, back, or both faces to be culled
+
+    Parameters
+    ----------
+    mode : str
+        Culling mode. Can be "front", "back", or "front_and_back".
+    """
+    gl.glCullFace(_gl_attr(mode))
+
+
+def set_line_width(width=1.):
+    """Set line width
+
+    Parameters
+    ----------
+    width : float
+        The line width.
+    """
+    gl.glLineWidth(float(width))
+
+
+def set_polygon_offset(factor=0., units=0.):
+    """Set the scale and units used to calculate depth values
+
+    Parameters
+    ----------
+    factor : float
+        Scale factor used to create a variable depth offset for each polygon.
+    units : float
+        Multiplied by an implementation-specific value to create a constant
+        depth offset.
+    """
+    gl.glPolygonOffset(float(factor), float(units))
+
+
+###############################################################################
+# FRAGMENT/SCREEN
+
+#
+# glClear, glClearColor, glClearDepthf, glClearStencil
+#
+
+def clear(color=True, depth=True, stencil=True):
+    """Clear the screen buffers
+
+    This is a wrapper for gl.glClear.
+
+    Parameters
+    ----------
+    color : bool | str | tuple | instance of Color
+        Clear the color buffer bit. If not bool, ``set_clear_color`` will
+        be used to set the color clear value.
+    depth : bool | float
+        Clear the depth buffer bit. If float, ``set_clear_depth`` will
+        be used to set the depth clear value.
+    stencil : bool | int
+        Clear the stencil buffer bit. If int, ``set_clear_stencil`` will
+        be used to set the stencil clear index.
+    """
+    bits = 0
+    if isinstance(color, np.ndarray) or bool(color):
+        if not isinstance(color, bool):
+            set_clear_color(color)
+        bits |= gl.GL_COLOR_BUFFER_BIT
+    if depth:
+        if not isinstance(depth, bool):
+            set_clear_depth(depth)
+        bits |= gl.GL_DEPTH_BUFFER_BIT
+    if stencil:
+        if not isinstance(stencil, bool):
+            set_clear_stencil(stencil)
+        bits |= gl.GL_STENCIL_BUFFER_BIT
+    gl.glClear(bits)
+
+
+def set_clear_color(color='black'):
+    """Set the screen clear color
+
+    This is a wrapper for gl.glClearColor.
+
+    Parameters
+    ----------
+    color : str | tuple | instance of Color
+        Color to use. See vispy.color.Color for options.
+    """
+    gl.glClearColor(*Color(color).rgba)
+
+
+def set_clear_depth(depth=1.0):
+    """Set the clear value for the depth buffer
+
+    This is a wrapper for gl.glClearDepth.
+
+    Parameters
+    ----------
+    depth : float
+        The depth to use.
+    """
+    gl.glClearDepth(float(depth))
+
+
+def set_clear_stencil(index=0):
+    """Set the clear value for the stencil buffer
+
+    This is a wrapper for gl.glClearStencil.
+
+    Parameters
+    ----------
+    index : int
+        The index to use when the stencil buffer is cleared.
+    """
+    gl.glClearStencil(int(index))
+
+
+# glBlendFunc(Separate), glBlendColor, glBlendEquation(Separate)
+
+def set_blend_func(srgb='one', drgb='zero',
+                   salpha=None, dalpha=None):
+    """Specify pixel arithmetic for RGB and alpha
+
+    Parameters
+    ----------
+    srgb : str
+        Source RGB factor.
+    drgb : str
+        Destination RGB factor.
+    salpha : str | None
+        Source alpha factor. If None, ``srgb`` is used.
+    dalpha : str
+        Destination alpha factor. If None, ``drgb`` is used.
+    """
+    salpha = srgb if salpha is None else salpha
+    dalpha = drgb if dalpha is None else dalpha
+    gl.glBlendFuncSeparate(_gl_attr(srgb), _gl_attr(drgb),
+                           _gl_attr(salpha), _gl_attr(dalpha))
+
+
+def set_blend_color(color):
+    """Set the blend color
+
+    Parameters
+    ----------
+    color : str | tuple | instance of Color
+        Color to use. See vispy.color.Color for options.
+    """
+    gl.glBlendColor(*Color(color).rgba)
+
+
+def set_blend_equation(mode_rgb, mode_alpha=None):
+    """Specify the equation for RGB and alpha blending
+
+    Parameters
+    ----------
+    mode_rgb : str
+        Mode for RGB.
+    mode_alpha : str | None
+        Mode for Alpha. If None, ``mode_rgb`` is used.
+
+    Notes
+    -----
+    See ``set_blend_equation`` for valid modes.
+    """
+    mode_alpha = mode_rgb if mode_alpha is None else mode_alpha
+    gl.glBlendEquationSeparate(_gl_attr(mode_rgb),
+                               _gl_attr(mode_alpha))
+
+
+# glScissor, glStencilFunc(Separate), glStencilMask(Separate),
+# glStencilOp(Separate),
+
+def set_scissor(x, y, w, h):
+    """Define the scissor box
+
+    Parameters
+    ----------
+    x : int
+        Left corner of the box.
+    y : int
+        Lower corner of the box.
+    w : int
+        The width of the box.
+    h : int
+        The height of the box.
+    """
+    gl.glScissor(int(x), int(y), int(w), int(h))
+
+
+def set_stencil_func(func='always', ref=0, mask=8, face='front_and_back'):
+    """Set front or back function and reference value
+
+    Parameters
+    ----------
+    func : str
+        See set_stencil_func.
+    ref : int
+        Reference value for the stencil test.
+    mask : int
+        Mask that is ANDed with ref and stored stencil value.
+    face : str
+        Can be 'front', 'back', or 'front_and_back'.
+    """
+    gl.glStencilFuncSeparate(_gl_attr(face), _gl_attr(func),
+                             int(ref), int(mask))
+
+
+def set_stencil_mask(mask=8, face='front_and_back'):
+    """Control the front or back writing of individual bits in the stencil
+
+    Parameters
+    ----------
+    mask : int
+        Mask that is ANDed with ref and stored stencil value.
+    face : str
+        Can be 'front', 'back', or 'front_and_back'.
+    """
+    gl.glStencilMaskSeparate(_gl_attr(face), int(mask))
+
+
+def set_stencil_op(sfail='keep', dpfail='keep', dppass='keep',
+                   face='front_and_back'):
+    """Set front or back stencil test actions
+
+    Parameters
+    ----------
+    sfail : str
+        Action to take when the stencil fails. Must be one of
+        'keep', 'zero', 'replace', 'incr', 'incr_wrap',
+        'decr', 'decr_wrap', or 'invert'.
+    dpfail : str
+        Action to take when the stencil passes.
+    dppass : str
+        Action to take when both the stencil and depth tests pass,
+        or when the stencil test passes and either there is no depth
+        buffer or depth testing is not enabled.
+    face : str
+        Can be 'front', 'back', or 'front_and_back'.
+    """
+    gl.glStencilOpSeparate(_gl_attr(face), _gl_attr(sfail),
+                           _gl_attr(dpfail), _gl_attr(dppass))
+
+
+# glDepthFunc, glDepthMask, glColorMask, glSampleCoverage
+
+def set_depth_func(func='less'):
+    """Specify the value used for depth buffer comparisons
+
+    Parameters
+    ----------
+    func : str
+        The depth comparison function. Must be one of 'never', 'less', 'equal',
+        'lequal', 'greater', 'gequal', 'notequal', or 'always'.
+    """
+    gl.glDepthFunc(_gl_attr(func))
+
+
+def set_depth_mask(flag):
+    """Toggle writing into the depth buffer
+
+    Parameters
+    ----------
+    flag : bool
+        Whether depth writing should be enabled.
+    """
+    gl.glDepthMask(_gl_bool(flag))
+
+
+def set_color_mask(red, green, blue, alpha):
+    """Toggle writing of frame buffer color components
+
+    Parameters
+    ----------
+    red : bool
+        Red toggle.
+    green : bool
+        Green toggle.
+    blue : bool
+        Blue toggle.
+    alpha : bool
+        Alpha toggle.
+    """
+    gl.glColorMask(_gl_bool(red), _gl_bool(green), _gl_bool(blue),
+                   _gl_bool(alpha))
+
+
+def set_sample_coverage(value=1.0, invert=False):
+    """Specify multisample coverage parameters
+
+    Parameters
+    ----------
+    value : float
+        Sample coverage value (will be clamped between 0. and 1.).
+    invert : bool
+        Specify if the coverage masks should be inverted.
+    """
+    gl.glSampleCoverage(float(value), _gl_bool(invert))
+
+
+###############################################################################
+# STATE
+
+#
+# glEnable/Disable
+#
+
+# NOTE: If these are updated to have things beyond glEnable/glBlendFunc
+# calls, set_preset_state will need to be updated to deal with it.
+_gl_presets = dict(
+    opaque=dict(depth_test=True, cull_face=False, blend=False),
+    translucent=dict(depth_test=True, cull_face=False, blend=True,
+                     blend_func=('src_alpha', 'one_minus_src_alpha')),
+    additive=dict(depth_test=False, cull_face=False, blend=True,
+                  blend_func=('src_alpha', 'one'),)
+)
+
+
+def get_state_presets():
+    """The available GL state presets
+
+    Returns
+    -------
+    presets : dict
+        The dictionary of presets usable with ``set_options``.
+    """
+    return deepcopy(_gl_presets)
+
+
+_known_state_names = ('depth_test', 'blend', 'blend_func')
+
+
+def set_state(preset=None, **kwargs):
+    """Set OpenGL rendering state, optionally using a preset
+
+    Parameters
+    ----------
+    preset : str | None
+        Can be one of ('opaque', 'translucent', 'additive') to use
+        use reasonable defaults for these typical use cases.
+    **kwargs : keyword arguments
+        Other supplied keyword arguments will override any preset defaults.
+        Options to be enabled or disabled should be supplied as booleans
+        (e.g., ``'depth_test=True'``, ``cull_face=False``), non-boolean
+        entries will be passed as arguments to ``set_*`` functions (e.g.,
+        ``blend_func=('src_alpha', 'one')`` will call ``set_blend_func``).
+
+    Notes
+    -----
+    This serves three purposes:
+
+      1. Set GL state using reasonable presets.
+      2. Wrapping glEnable/glDisable functionality.
+      3. Convienence wrapping of other ``gloo.set_*`` functions.
+
+    For example, one could do the following:
+
+        >>> from vispy import gloo
+        >>> gloo.set_state('translucent', depth_test=False, clear_color=(1, 1, 1, 1))  # noqa, doctest:+SKIP
+
+    This would take the preset defaults for 'translucent', turn depth testing
+    off (which would normally be on for that preset), and additionally
+    set the glClearColor parameter to be white.
+
+    Another example to showcase glEnable/glDisable wrapping:
+
+        >>> gloo.set_state(blend=True, depth_test=True, polygon_offset_fill=False)  # noqa, doctest:+SKIP
+
+    This would be equivalent to calling
+
+        >>> from vispy.gloo import gl
+        >>> gl.glDisable(gl.GL_BLEND)
+        >>> gl.glEnable(gl.GL_DEPTH_TEST)
+        >>> gl.glEnable(gl.GL_POLYGON_OFFSET_FILL)
+
+    Or here's another example:
+
+        >>> gloo.set_state(clear_color=(0, 0, 0, 1), blend=True, blend_func=('src_alpha', 'one'))  # noqa, doctest:+SKIP
+
+    Thus arbitrary GL state components can be set directly using ``set_state``.
+    Note that individual functions are exposed e.g., as ``set_clear_color``,
+    with some more informative docstrings about those particular functions.
+    """
+    kwargs = deepcopy(kwargs)
+    # Load preset, if supplied
+    if preset is not None:
+        _check_valid('preset', preset, tuple(list(_gl_presets.keys())))
+        for key, val in _gl_presets[preset].items():
+            # only overwrite user's input with preset if user's input is None
+            if key not in kwargs:
+                kwargs[key] = val
+
+    # cull_face is an exception because GL_CULL_FACE and glCullFace both exist
+    if 'cull_face' in kwargs:
+        cull_face = kwargs.pop('cull_face')
+        if isinstance(cull_face, bool):
+            func = gl.glEnable if cull_face else gl.glDisable
+            func(_gl_attr('cull_face'))
+        else:
+            set_cull_face(*_to_args(cull_face))
+
+    # Now deal with other non-glEnable/glDisable args
+    for s in _setters:
+        if s in kwargs:
+            args = _to_args(kwargs.pop(s))
+            # these actually need tuples
+            if s in ('blend_color', 'clear_color') and \
+                    not isinstance(args[0], string_types):
+                args = [args]
+            globals()['set_' + s](*args)
+
+    # check values and translate
+    for key, val in kwargs.items():
+        func = gl.glEnable if val else gl.glDisable
+        func(_gl_attr(key))
+
+
+#
+# glFinish, glFlush, glGetParameter, glReadPixels, glHint
+#
+
+def finish():
+    """Wait for GL commands to to finish
+
+    This is a wrapper for glFinish().
+    """
+    gl.glFinish()
+
+
+def flush():
+    """Flush GL commands
+
+    This is a wrapper for glFlush().
+    """
+    gl.glFlush()
+
+
+def get_parameter(name):
+    """Get OpenGL parameter value
+
+    Parameters
+    ----------
+    name : str
+        The name of the parameter to get.
+    """
+    if not isinstance(name, string_types):
+        raise TypeError('name bust be a string')
+    return gl.glGetParameter(_gl_attr(name))
+
+
+def read_pixels(viewport=None, alpha=True):
+    """Read pixels from the front buffer
+
+    Parameters
+    ----------
+    viewport : array-like | None
+        4-element list of x, y, w, h parameters. If None (default),
+        the current GL viewport will be queried and used.
+    alpha : bool
+        If True (default), the returned array has 4 elements (RGBA).
+        Otherwise, it has 3 (RGB).
+
+    Returns
+    -------
+    pixels : array
+        3D array of pixels in np.uint8 format.
+    """
+    if viewport is None:
+        viewport = get_parameter('viewport')
+    viewport = np.array(viewport, int)
+    if viewport.ndim != 1 or viewport.size != 4:
+        raise ValueError('viewport should be 1D 4-element array-like, not %s'
+                         % (viewport,))
+    x, y, w, h = viewport
+    gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1)  # PACK, not UNPACK
+    if alpha:  # gl.GL_RGBA
+        im = gl.glReadPixels(x, y, w, h, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE)
+    else:  # gl.gl_RGB
+        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)
+
+    if alpha:
+        im.shape = h, w, 4  # RGBA
+    else:
+        im.shape = h, w, 3  # RGB
+    im = im[::-1, :, :]  # flip the image
+    return im
+
+
+def set_hint(target, mode):
+    """Set OpenGL drawing hint
+
+    Parameters
+    ----------
+    target : str
+        The target (e.g., 'fog_hint', 'line_smooth_hint', 'point_smooth_hint').
+    mode : str
+        The mode to set (e.g., 'fastest', 'nicest', 'dont_care').
+    """
+    if not all(isinstance(tm, string_types) for tm in (target, mode)):
+        raise TypeError('target and mode must both be strings')
+    gl.glHint(_gl_attr(target), _gl_attr(mode))
+
+
+###############################################################################
+# Current OpenGL configuration
+
+def get_gl_configuration():
+    """Read the current gl configuration
+
+    This function uses constants that are not in the OpenGL ES 2.1
+    namespace, so only use this on desktop systems.
+
+    Returns
+    -------
+    config : dict
+        The currently active OpenGL configuration.
+    """
+    # XXX eventually maybe we can ask `gl` whether or not we can access these
+    gl.check_error('pre-config check')
+    config = dict()
+    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
+    fb_param = gl.glGetFramebufferAttachmentParameter
+    # copied since they aren't in ES:
+    GL_FRONT_LEFT = 1024
+    GL_DEPTH = 6145
+    GL_STENCIL = 6146
+    GL_SRGB = 35904
+    GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 33296
+    GL_STEREO = 3123
+    GL_DOUBLEBUFFER = 3122
+    sizes = dict(red=(GL_FRONT_LEFT, 33298),
+                 green=(GL_FRONT_LEFT, 33299),
+                 blue=(GL_FRONT_LEFT, 33300),
+                 alpha=(GL_FRONT_LEFT, 33301),
+                 depth=(GL_DEPTH, 33302),
+                 stencil=(GL_STENCIL, 33303))
+    for key, val in sizes.items():
+        config[key + '_size'] = fb_param(gl.GL_FRAMEBUFFER, val[0], val[1])
+    val = fb_param(gl.GL_FRAMEBUFFER, GL_FRONT_LEFT,
+                   GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
+    if val not in (gl.GL_LINEAR, GL_SRGB):
+        raise RuntimeError('unknown value for SRGB: %s' % val)
+    config['srgb'] = True if val == GL_SRGB else False  # GL_LINEAR
+    config['stereo'] = True if gl.glGetParameter(GL_STEREO) else False
+    config['double_buffer'] = (True if gl.glGetParameter(GL_DOUBLEBUFFER)
+                               else False)
+    config['samples'] = gl.glGetParameter(gl.GL_SAMPLES)
+    gl.check_error('post-config check')
+    return config
+
+
+def check_error():
+    """Check for OpenGL errors
+
+    For efficiency, errors are only checked periodically. This forces
+    a check for OpenGL errors.
+    """
+    gl.check_error('gloo check')
diff --git a/vispy/html/static/js/vispy.js b/vispy/html/static/js/vispy.js
new file mode 100644
index 0000000..81e75af
--- /dev/null
+++ b/vispy/html/static/js/vispy.js
@@ -0,0 +1,190 @@
+function get_pos(c, e) {
+    var rect = c.getBoundingClientRect();
+    return [e.clientX - rect.left, e.clientY - rect.top];
+};
+
+function get_modifiers(e) {
+    var modifiers = [];
+    if (e.altKey) modifiers.push("alt");
+    if (e.ctrlKey) modifiers.push("ctrl");
+    if (e.metaKey) modifiers.push("meta");
+    if (e.shiftKey) modifiers.push("shift");
+    return modifiers;
+};
+
+function get_key(e) {
+    var keynum = null;
+    if (window.event) { // IE
+        keynum = e.keyCode;
+    } else if (e.which) { // Netscape/Firefox/Opera
+        keynum = e.which;
+    }
+    return keynum;
+};
+
+function gen_mouse_event(c, e, type) {
+    if (c._eventinfo.is_button_pressed)
+        var button = e.button;
+    else
+        button = null;
+    var pos = get_pos(c, e);
+    var modifiers = get_modifiers(e);
+    var press_event = c._eventinfo.press_event;
+    var event = {
+        "source": "browser",
+        "event":
+        // Mouse Event
+        {
+            "name": "MouseEvent",
+            "properties": {
+                "type": type,
+                "pos": pos,
+                "button": e.button,
+                "is_dragging": press_event != null,
+                "modifiers": modifiers,
+                "press_event": press_event,
+                "delta": null,
+            }
+        }
+    };
+    return event;
+};
+
+function gen_key_event(c, e, type) {
+    var modifiers = get_modifiers(e);
+    var key_code = get_key(e);
+    var key_text = String.fromCharCode(key_code);
+    var event = {
+        "source": "browser",
+        "event":
+        // Key Event
+        {
+            "name": "KeyEvent",
+            "properties": {
+                "type": type,
+                "key": key_code,
+                "text": key_text,
+                "modifiers": modifiers,
+            }
+        }
+    };
+    return event;
+};
+
+function send_timer_event(w) {
+    var event = {
+        "event":
+        // Poll Event
+        {
+            "name": "PollEvent",
+        }
+    };
+    w.send(event);
+};
+
+require(["widgets/js/widget"], function(WidgetManager) {
+    var Widget = IPython.DOMWidgetView.extend({
+        render: function() {
+            this.$canvas = $('<canvas />')
+                .attr('id', 'canvas')
+                .attr('tabindex', '1')
+                .appendTo(this.$el);
+
+            this.c = this.$canvas[0];
+            this.c.width = this.model.get("width");
+            this.c.height = this.model.get("height");
+            this.canvas2d = this.c.getContext('2d');
+
+            this.c._eventinfo = {
+                'type': null,
+                'pos': null,
+                'button': null,
+                'is_dragging': null,
+                'key': null,
+                'modifiers': [],
+                'press_event': null,
+                'delta': [],
+                'is_button_pressed': 0,
+                'last_pos': [-1, -1],
+            };
+
+            this.c.interval = 50.0;  // Arbitrary for now
+            this.c.timer = setInterval(send_timer_event, this.c.interval, this);
+        },
+
+        events: {
+            'mousemove': 'mouse_move',
+            'mousedown': 'mouse_press',
+            'mouseup': 'mouse_release',
+            'mousewheel': 'mouse_wheel',
+            'keydown': 'key_press',
+            'keyup': 'key_release',
+        },
+
+        mouse_move: function(e) {
+            var event = gen_mouse_event(this.c, e, "mouse_move");
+            var pos = event.event.properties.pos;
+            var last_pos = this.c._eventinfo.last_pos;
+            if (pos[0] != last_pos[0] || pos[1] != last_pos[1])
+                this.send(event);
+            this.c._eventinfo.last_pos = pos;
+        },
+
+        mouse_press: function(e) {
+            ++this.c._eventinfo.is_button_pressed;
+            var event = gen_mouse_event(this.c, e, "mouse_press");
+            this.c._eventinfo.press_event = event.event.properties;
+            this.send(event);
+        },
+
+        mouse_release: function(e) {
+            --this.c._eventinfo.is_button_pressed;
+            var event = gen_mouse_event(this.c, e, "mouse_release");
+            this.c._eventinfo.press_event = null;
+            this.send(event);
+        },
+
+        mouse_wheel: function(e) {
+            var event = gen_mouse_event(this.c, e, "mouse_wheel");
+            var delta = [e.originalEvent.wheelDeltaX / 120, e.originalEvent.wheelDeltaY / 120];
+            event.event.properties.delta = delta;
+            this.send(event);
+            // Keep page from scrolling
+            e.preventDefault();
+        },
+
+        key_press: function(e) {
+            var event = gen_key_event(this.c, e, "key_press");
+            this.send(event);
+        },
+
+        key_release: function(e) {
+            var event = gen_key_event(this.c, e, "key_release");
+            this.send(event);
+        },
+
+        // Update, whenever value attribute of our widget changes
+        update: function() {
+            if(this.model.get("is_closing") == true)
+            {
+                clearInterval(this.c.timer);  // Remove existing timer
+                return;
+            }
+
+            this.c.width = this.model.get("width");
+            this.c.height = this.model.get("height");
+            var new_int = this.model.get("interval");
+            if(this.c.interval != new_int)  // Update the interval
+            {
+                this.c.interval = new_int;
+                clearInterval(this.c.timer);  // Remove existing and set new one
+                this.c.timer = setInterval(send_timer_event, this.c.interval, this);
+            }
+            var img_str = this.model.get("value");
+            var img = new Image();
+            img.src = "data:image/png;base64," + img_str;
+            this.canvas2d.drawImage(img, 0, 0);
+        },
+    })
+    WidgetManager.register_widget_view("Widget", Widget);
+});
diff --git a/vispy/io/__init__.py b/vispy/io/__init__.py
new file mode 100644
index 0000000..4e93fc8
--- /dev/null
+++ b/vispy/io/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Utilities related to data reading, writing, fetching, and generation.
+"""
+
+__all__ = ['imread', 'imsave', 'load_iris', 'load_crate',
+           'load_data_file', 'read_mesh', 'read_png', 'write_mesh',
+           'write_png']
+
+from os import path as _op
+
+from .datasets import load_iris, load_crate, load_data_file  # noqa
+from .mesh import read_mesh, write_mesh  # noqa
+from .image import (read_png, write_png, imread, imsave, _make_png,  # noqa
+                    _check_img_lib)  # noqa
+
+_data_dir = _op.join(_op.dirname(__file__), '_data')
diff --git a/vispy/io/_data/spatial-filters.npy b/vispy/io/_data/spatial-filters.npy
new file mode 100644
index 0000000..0e468c6
Binary files /dev/null and b/vispy/io/_data/spatial-filters.npy differ
diff --git a/vispy/io/datasets.py b/vispy/io/datasets.py
new file mode 100644
index 0000000..f0b591e
--- /dev/null
+++ b/vispy/io/datasets.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+from os import path as op
+
+from ..util.fetching import load_data_file
+
+# This is the package data dir, not the dir for config, etc.
+DATA_DIR = op.join(op.dirname(__file__), '_data')
+
+
+def load_iris():
+    """Load the iris dataset
+
+    Returns
+    -------
+    iris : NpzFile
+        data['data'] : a (150, 4) NumPy array with the iris' features
+        data['group'] : a (150,) NumPy array with the iris' group
+    """
+    return np.load(load_data_file('iris/iris.npz'))
+
+
+def load_crate():
+    """Load an image of a crate
+
+    Returns
+    -------
+    crate : array
+        256x256x3 crate image.
+    """
+    return np.load(load_data_file('orig/crate.npz'))['crate']
diff --git a/vispy/io/image.py b/vispy/io/image.py
new file mode 100644
index 0000000..99fa6a2
--- /dev/null
+++ b/vispy/io/image.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+# Author: Luke Campagnola
+# -----------------------------------------------------------------------------
+
+
+import struct
+import zlib
+import numpy as np
+
+from ..ext.png import Reader
+
+
+def _make_png(data, level=6):
+    """Convert numpy array to PNG byte array.
+
+    Parameters
+    ----------
+    data : numpy.ndarray
+        Data must be (H, W, 3 | 4) with dtype = np.ubyte (np.uint8)
+    level : int
+        https://docs.python.org/2/library/zlib.html#zlib.compress
+        An integer from 0 to 9 controlling the level of compression:
+
+            * 1 is fastest and produces the least compression,
+            * 9 is slowest and produces the most.
+            * 0 is no compression.
+
+        The default value is 6.
+
+    Returns
+    -------
+    png : array
+        PNG formatted array
+    """
+    # Eventually we might want to use ext/png.py for this, but this
+    # routine *should* be faster b/c it's speacialized for our use case
+
+    def mkchunk(data, name):
+        if isinstance(data, np.ndarray):
+            size = data.nbytes
+        else:
+            size = len(data)
+        chunk = np.empty(size + 12, dtype=np.ubyte)
+        chunk.data[0:4] = np.array(size, '>u4').tostring()
+        chunk.data[4:8] = name.encode('ASCII')
+        chunk.data[8:8 + size] = data
+        # and-ing may not be necessary, but is done for safety:
+        # https://docs.python.org/3/library/zlib.html#zlib.crc32
+        chunk.data[-4:] = np.array(zlib.crc32(chunk[4:-4]) & 0xffffffff,
+                                   '>u4').tostring()
+        return chunk
+
+    if data.dtype != np.ubyte:
+        raise TypeError('data.dtype must be np.ubyte (np.uint8)')
+
+    dim = data.shape[2]  # Dimension
+    if dim not in (3, 4):
+        raise TypeError('data.shape[2] must be in (3, 4)')
+
+    # www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR
+    if dim == 4:
+        ctyp = 0b0110  # RGBA
+    else:
+        ctyp = 0b0010  # RGB
+
+    # www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
+    header = b'\x89PNG\x0d\x0a\x1a\x0a'  # header
+
+    h, w = data.shape[:2]
+    depth = data.itemsize * 8
+    ihdr = struct.pack('!IIBBBBB', w, h, depth, ctyp, 0, 0, 0)
+    c1 = mkchunk(ihdr, 'IHDR')
+
+    # www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IDAT
+    # insert filter byte at each scanline
+    idat = np.empty((h, w * dim + 1), dtype=np.ubyte)
+    idat[:, 1:] = data.reshape(h, w * dim)
+    idat[:, 0] = 0
+
+    comp_data = zlib.compress(idat, level)
+    c2 = mkchunk(comp_data, 'IDAT')
+    c3 = mkchunk(np.empty((0,), dtype=np.ubyte), 'IEND')
+
+    # concatenate
+    lh = len(header)
+    png = np.empty(lh + c1.nbytes + c2.nbytes + c3.nbytes, dtype=np.ubyte)
+    png.data[:lh] = header
+    p = lh
+
+    for chunk in (c1, c2, c3):
+        png[p:p + len(chunk)] = chunk
+        p += chunk.nbytes
+
+    return png
+
+
+def read_png(filename):
+    """Read a PNG file to RGB8 or RGBA8
+
+    Unlike imread, this requires no external dependencies.
+
+    Parameters
+    ----------
+    filename : str
+        File to read.
+
+    Returns
+    -------
+    data : array
+        Image data.
+
+    See also
+    --------
+    write_png, imread, imsave
+    """
+    x = Reader(filename)
+    try:
+        alpha = x.asDirect()[3]['alpha']
+        if alpha:
+            y = x.asRGBA8()[2]
+            n = 4
+        else:
+            y = x.asRGB8()[2]
+            n = 3
+        y = np.array([yy for yy in y], np.uint8)
+    finally:
+        x.file.close()
+    y.shape = (y.shape[0], y.shape[1] // n, n)
+    return y
+
+
+def write_png(filename, data):
+    """Write a PNG file
+
+    Unlike imsave, this requires no external dependencies.
+
+    Parameters
+    ----------
+    filename : str
+        File to save to.
+    data : array
+        Image data.
+
+    See also
+    --------
+    read_png, imread, imsave
+    """
+    data = np.asarray(data)
+    if not data.ndim == 3 and data.shape[-1] in (3, 4):
+        raise ValueError('data must be a 3D array with last dimension 3 or 4')
+    with open(filename, 'wb') as f:
+        f.write(_make_png(data))  # Save array with make_png
+
+
+def imread(filename, format=None):
+    """Read image data from disk
+
+    Requires imageio or PIL.
+
+    Parameters
+    ----------
+    filename : str
+        Filename to read.
+    format : str | None
+        Format of the file. If None, it will be inferred from the filename.
+
+    Returns
+    -------
+    data : array
+        Image data.
+
+    See also
+    --------
+    imsave, read_png, write_png
+    """
+    imageio, PIL = _check_img_lib()
+    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")
+        return a
+    else:
+        raise RuntimeError("imread requires the imageio or PIL package.")
+
+
+def imsave(filename, im, format=None):
+    """Save image data to disk
+
+    Requires imageio or PIL.
+
+    Parameters
+    ----------
+    filename : str
+        Filename to write.
+    im : array
+        Image data.
+    format : str | None
+        Format of the file. If None, it will be inferred from the filename.
+
+    See also
+    --------
+    imread, read_png, write_png
+    """
+    # Import imageio or PIL
+    imageio, PIL = _check_img_lib()
+    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 _check_img_lib():
+    """Utility to search for imageio or PIL"""
+    # Import imageio or PIL
+    imageio = PIL = None
+    try:
+        import imageio
+    except ImportError:
+        try:
+            import PIL.Image
+        except ImportError:
+            pass
+    return imageio, PIL
diff --git a/vispy/io/mesh.py b/vispy/io/mesh.py
new file mode 100644
index 0000000..d76cdce
--- /dev/null
+++ b/vispy/io/mesh.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+""" Reading and writing of data like images and meshes.
+"""
+
+from os import path as op
+
+from .wavefront import WavefrontReader, WavefrontWriter
+
+
+def read_mesh(fname):
+    """Read mesh data from file.
+
+    Parameters
+    ----------
+    fname : str
+        File name to read. Format will be inferred from the filename.
+        Currently only '.obj' and '.obj.gz' are supported.
+
+    Returns
+    -------
+    vertices : array
+        Vertices.
+    faces : array | None
+        Triangle face definitions.
+    normals : array
+        Normals for the mesh.
+    texcoords : array | None
+        Texture coordinates.
+    """
+    # Check format
+    fmt = op.splitext(fname)[1].lower()
+    if fmt == '.gz':
+        fmt = op.splitext(op.splitext(fname)[0])[1].lower()
+
+    if fmt in ('.obj'):
+        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.' % fmt)
+
+
+def write_mesh(fname, vertices, faces, normals, texcoords, name='',
+               format='obj', overwrite=False):
+    """ Write mesh data to file.
+
+    Parameters
+    ----------
+    fname : str
+        Filename to write. Must end with ".obj" or ".gz".
+    vertices : array
+        Vertices.
+    faces : array | None
+        Triangle face definitions.
+    normals : array
+        Normals for the mesh.
+    texcoords : array | None
+        Texture coordinates.
+    name : str
+        Name of the object.
+    format : str
+        Currently only "obj" is supported.
+    overwrite : bool
+        If the file exists, overwrite it.
+    """
+    # Check file
+    if op.isfile(fname) and not overwrite:
+        raise IOError('file "%s" exists, use overwrite=True' % fname)
+
+    # Check format
+    if format not in ('obj'):
+        raise ValueError('Only "obj" format writing currently supported')
+    WavefrontWriter.write(fname, vertices, faces, normals, texcoords, name)
diff --git a/vispy/shaders/__init__.py b/vispy/io/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/io/tests/__init__.py
diff --git a/vispy/io/tests/test_image.py b/vispy/io/tests/test_image.py
new file mode 100644
index 0000000..e1d5220
--- /dev/null
+++ b/vispy/io/tests/test_image.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+import numpy as np
+from numpy.testing import assert_array_equal, assert_allclose
+from os import path as op
+import warnings
+
+from vispy.io import load_crate, imsave, imread, read_png, write_png
+from vispy.testing import requires_img_lib
+from vispy.util import _TempDir
+
+temp_dir = _TempDir()
+
+
+def test_make_png():
+    """ Test to ensure that make_png functions correctly.
+    """
+    # Save random RGBA and RGB arrays onto disk as PNGs using make_png.
+    # Read them back with an image library and check whether the array
+    # saved is equal to the array read.
+
+    # Create random RGBA array as type ubyte
+    rgba_save = np.random.randint(256, size=(100, 100, 4)).astype(np.ubyte)
+    # Get rid of the alpha for RGB
+    rgb_save = rgba_save[:, :, :3]
+    # Output file should be in temp
+    png_out = op.join(temp_dir, 'random.png')
+
+    # write_png implicitly tests _make_png
+    for rgb_a in (rgba_save, rgb_save):
+        write_png(png_out, rgb_a)
+        rgb_a_read = read_png(png_out)
+        assert_array_equal(rgb_a, rgb_a_read)
+
+
+ at requires_img_lib()
+def test_read_write_image():
+    """Test reading and writing of images"""
+    fname = op.join(temp_dir, 'out.png')
+    im1 = load_crate()
+    imsave(fname, im1, format='png')
+    with warnings.catch_warnings(record=True):  # PIL unclosed file
+        im2 = imread(fname)
+    assert_allclose(im1, im2)
diff --git a/vispy/io/tests/test_io.py b/vispy/io/tests/test_io.py
new file mode 100644
index 0000000..9be5a03
--- /dev/null
+++ b/vispy/io/tests/test_io.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+import numpy as np
+from os import path as op
+from nose.tools import assert_equal, assert_raises
+from numpy.testing import assert_allclose, assert_array_equal
+
+from vispy.io import write_mesh, read_mesh, load_data_file
+from vispy.geometry import _fast_cross_3d
+from vispy.util import _TempDir
+
+temp_dir = _TempDir()
+
+
+def test_wavefront():
+    """Test wavefront reader"""
+    fname_mesh = load_data_file('orig/triceratops.obj.gz')
+    fname_out = op.join(temp_dir, 'temp.obj')
+    mesh1 = read_mesh(fname_mesh)
+    assert_raises(IOError, read_mesh, 'foo.obj')
+    assert_raises(ValueError, read_mesh, op.abspath(__file__))
+    assert_raises(ValueError, write_mesh, fname_out, *mesh1, format='foo')
+    write_mesh(fname_out, mesh1[0], mesh1[1], mesh1[2], mesh1[3])
+    assert_raises(IOError, write_mesh, fname_out, *mesh1)
+    write_mesh(fname_out, *mesh1, overwrite=True)
+    mesh2 = read_mesh(fname_out)
+    assert_equal(len(mesh1), len(mesh2))
+    for m1, m2 in zip(mesh1, mesh2):
+        if m1 is None:
+            assert_equal(m2, None)
+        else:
+            assert_allclose(m1, m2, rtol=1e-5)
+    # test our efficient normal calculation routine
+    assert_allclose(mesh1[2], _slow_calculate_normals(mesh1[0], mesh1[1]),
+                    rtol=1e-7, atol=1e-7)
+
+
+def _slow_calculate_normals(rr, tris):
+    """Efficiently compute vertex normals for triangulated surface"""
+    # first, compute triangle normals
+    rr = rr.astype(np.float64)
+    r1 = rr[tris[:, 0], :]
+    r2 = rr[tris[:, 1], :]
+    r3 = rr[tris[:, 2], :]
+    tri_nn = np.cross((r2 - r1), (r3 - r1))
+
+    #   Triangle normals and areas
+    size = np.sqrt(np.sum(tri_nn * tri_nn, axis=1))
+    zidx = np.where(size == 0)[0]
+    size[zidx] = 1.0  # prevent ugly divide-by-zero
+    tri_nn /= size[:, np.newaxis]
+
+    # accumulate the normals
+    nn = np.zeros((len(rr), 3))
+    for p, verts in enumerate(tris):
+        nn[verts] += tri_nn[p, :]
+    size = np.sqrt(np.sum(nn * nn, axis=1))
+    size[size == 0] = 1.0  # prevent ugly divide-by-zero
+    nn /= size[:, np.newaxis]
+    return nn
+
+
+def test_huge_cross():
+    """Test cross product with lots of elements
+    """
+    x = np.random.rand(100000, 3)
+    y = np.random.rand(1, 3)
+    z = np.cross(x, y)
+    zz = _fast_cross_3d(x, y)
+    assert_array_equal(z, zz)
diff --git a/vispy/util/dataio/wavefront.py b/vispy/io/wavefront.py
similarity index 72%
rename from vispy/util/dataio/wavefront.py
rename to vispy/io/wavefront.py
index 6b206da..ac270ae 100644
--- a/vispy/util/dataio/wavefront.py
+++ b/vispy/io/wavefront.py
@@ -1,14 +1,14 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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
+The wavefront format is quite powerful and allows a wide variety of surfaces
 to be described.
 
 This implementation does only supports mesh stuff, so no nurbs etc. Further,
@@ -18,200 +18,188 @@ The classes are written with compatibility of Python3 in mind.
 
 """
 
-import time
 import numpy as np
+import time
+from os import path as op
 
+from ..ext.gzip_open import gzip_open
+from ..geometry import _calculate_normals
+from ..util import logger
 
 
 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 
+
+        # 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)
-        
+    def read(cls, fname):
+        """ read(fname, fmt)
+
         This classmethod is the entry point for reading OBJ files.
-        
+
         Parameters
         ----------
-        fname : string
+        fname : str
             The name of the file to read.
-        
+        fmt : str
+            Can be "obj" or "gz" to specify the file format.
         """
-        
-        t0 = time.time()
-        
         # Open file
-        f = open(fname, 'rb')
-        try:
-            reader = WavefrontReader(f)
-            while True:
-                reader.readLine()
-        except EOFError:
-            pass
-        finally:
-            f.close()
-        
+        fmt = op.splitext(fname)[1].lower()
+        assert fmt in ('.obj', '.gz')
+        opener = open if fmt == '.obj' else gzip_open
+        with opener(fname, 'rb') as f:
+            try:
+                reader = WavefrontReader(f)
+                while True:
+                    reader.readLine()
+            except EOFError:
+                pass
+
         # Done
+        t0 = time.time()
         mesh = reader.finish()
-        #print('reading mesh took ' + str(time.time()-t0) + ' seconds')
+        logger.debug('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) )
+            # self._vertices.append( *self.readTuple(line) )
+            self._v.append(self.readTuple(line))
         elif line.startswith('vt '):
-            self._vt.append( self.readTuple(line, 3) )
+            self._vt.append(self.readTuple(line, 3))
         elif line.startswith('vn '):
-            self._vn.append( self.readTuple(line) )
+            self._vn.append(self.readTuple(line))
         elif line.startswith('f '):
-            self._faces.append( self.readFace(line) )
+            self._faces.append(self.readFace(line))
         elif line.startswith('#'):
-            pass # Comment
+            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
+            logger.warning('Notice reading .OBJ: material properties are '
+                           'ignored.')
+        elif any(line.startswith(x) for x in ('g ', 's ', 'o ', 'usemtl ')):
+            pass  # Ignore groups and smoothing groups, obj names, material
         elif not line.strip():
             pass
         else:
-            print('Notice reading .OBJ: ignoring %s command.' % line.strip())
-    
-    
+            logger.warning('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]]
-    
-    
+        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] )
+                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] )
+                    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.')
+                        logger.warning('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] )
+                    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.')
+                        logger.warning('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.')
-        
+            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
+        if i > 0:
+            return i - 1
         else:
-            return ref+i
-    
-    
+            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]
+            # ensure it's always 2D so we can use our methods
+            faces = np.arange(0, vertices.size, dtype=np.uint32)[:, np.newaxis]
+        normals = _calculate_normals(vertices, faces)
         return normals
-    
 
     def finish(self):
-        """ Converts gathere lists to numpy arrays and creates 
+        """ Converts gathere lists to numpy arrays and creates
         BaseMesh instance.
         """
-        if True:
-            self._vertices = np.array(self._vertices, 'float32')
+        self._vertices = np.array(self._vertices, 'float32')
         if self._faces:
             self._faces = np.array(self._faces, 'uint32')
         else:
@@ -226,38 +214,39 @@ class WavefrontReader(object):
             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.
+            The filename to write to. Must end with ".obj" or ".gz".
         vertices : numpy array
             The vertex data
         faces : numpy array
             The face data
         texcoords : numpy array
             The texture coordinate per vertex
-        name : string
+        name : str
             The name of the object (e.g. 'teapot')
-        
         """
-        
         # Open file
-        f = open(fname, 'wb')
+        fmt = op.splitext(fname)[1].lower()
+        if fmt not in ('.obj', '.gz'):
+            raise ValueError('Filename must end with .obj or .gz, not "%s"'
+                             % (fmt,))
+        opener = open if fmt == '.obj' else gzip_open
+        f = opener(fname, 'wb')
         try:
             writer = WavefrontWriter(f)
             writer.writeMesh(vertices, faces, normals, texcoords, name)
@@ -265,16 +254,14 @@ class WavefrontWriter(object):
             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).
         """
@@ -286,45 +273,43 @@ class WavefrontWriter(object):
         # 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]
+        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])
+            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])
+            val = ' '.join(['%i//%i' % (v, v) for v in val])
         elif self._hasValues:
-            val = ' '.join(['%i/%i' % (v,v) for v in val])
+            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
+        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)
@@ -337,8 +322,7 @@ class WavefrontWriter(object):
         else:
             stats.append('no normals')
         stats.append('%i faces' % faces.shape[0])
-        
-        
+
         # Write header
         self.writeLine('# Wavefront OBJ file')
         self.writeLine('# Created by vispy.')
@@ -349,14 +333,14 @@ class WavefrontWriter(object):
             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')   
+                self.writeTuple(normals[i], 'vn')
         if self._hasValues:
             for i in range(N):
                 self.writeTuple(values[i], 'vt')
diff --git a/vispy/mpl_plot/__init__.py b/vispy/mpl_plot/__init__.py
new file mode 100644
index 0000000..921eeb7
--- /dev/null
+++ b/vispy/mpl_plot/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Matplotlib plotting backend wrapper.
+
+This module enables converting matplotlib plotting commands to
+vispy plotting commands. Support is experimental and incomplete,
+proceed with caution.
+"""
+
+__all__ = ['show']
+
+try:
+    from matplotlib.pyplot import *  # noqa
+except ImportError:
+    def show():
+        raise ImportError('matplotlib could not be found')
+else:
+    from ._mpl_to_vispy import show  # noqa
diff --git a/vispy/mpl_plot/_mpl_to_vispy.py b/vispy/mpl_plot/_mpl_to_vispy.py
new file mode 100644
index 0000000..0faa3e0
--- /dev/null
+++ b/vispy/mpl_plot/_mpl_to_vispy.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+import base64
+import warnings
+
+try:
+    import matplotlib.pyplot as plt
+    from ..ext.mplexporter import Exporter, Renderer
+except ImportError as exp:
+    Exporter = None
+    Renderer = object
+    has_mplexporter = False
+    why_not = str(exp)
+else:
+    has_mplexporter = True
+    why_not = None
+
+from ..ext.six import BytesIO
+from ..color import Color
+from ..io import read_png
+
+from ..scene.visuals import Line, Markers, Text, Image
+from ..scene.widgets import ViewBox
+from ..scene.transforms import STTransform
+from ..scene import SceneCanvas
+from ..testing import has_matplotlib
+
+
+def _check_coords(coords, valid):
+    if coords not in valid:
+        raise RuntimeError('Coords must be %s, not %s' % (valid, coords))
+
+
+class VispyRenderer(Renderer):
+    def __init__(self, *args, **kwargs):
+        self._line_count = 0
+        self._axs = {}
+        Renderer.__init__(self, *args, **kwargs)
+
+    def open_figure(self, fig, props):
+        self._dpi = props['dpi']
+        size = (props['figwidth'] * self._dpi,
+                props['figheight'] * self._dpi)
+        self.canvas = SceneCanvas(size=size, show=True, keys='interactive',
+                                  bgcolor='lightgray')
+
+        @self.canvas.events.resize.connect
+        def on_resize(event):
+            self._resize(*event.size)
+        self.canvas.events.resize.connect(on_resize)
+
+    def close_figure(self, fig):
+        # self.canvas.close()
+        pass  # don't do this, it closes when done rendering
+
+    def open_axes(self, ax, props):
+        bounds = np.array(props['bounds'])
+        bounds[1] = 1. - bounds[1] - bounds[3]
+        xlim = props['xlim']
+        ylim = props['ylim']
+        # for a in props['axes']:
+        #    a['position']  # add borders
+        vb = ViewBox(parent=self.canvas.scene, border_color='black',
+                     bgcolor=props['axesbg'])
+        vb.clip_method = 'fbo'  # necessary for bgcolor
+        vb.camera.rect = (xlim[0], ylim[0],
+                          xlim[1] - xlim[0], ylim[1] - ylim[0])
+        ax_dict = dict(ax=ax, bounds=bounds, vb=vb, lims=xlim+ylim)
+        self._axs[ax] = ax_dict
+        self._resize(*self.canvas.size)
+
+    def _resize(self, w, h):
+        for ax in self._axs.values():
+            ax['vb'].pos = (w * ax['bounds'][0], h * ax['bounds'][1])
+            ax['vb'].size = (w * ax['bounds'][2], h * ax['bounds'][3])
+
+    def close_axes(self, ax):
+        # self._axs.pop(ax)['vb'].parent = []
+        pass  # don't do anything, or all plots get closed (!)
+
+    def open_legend(self, legend, props):
+        raise NotImplementedError('Legends not supported yet')
+
+    def close_legend(self, legend):
+        pass
+
+    def draw_image(self, imdata, extent, coordinates, style, mplobj=None):
+        _check_coords(coordinates, 'data')
+        imdata = read_png(BytesIO(base64.b64decode(imdata.encode('utf-8'))))
+        assert imdata.ndim == 3 and imdata.shape[2] == 4
+        imdata[:, :, 3] = (imdata[:, :, 3] *
+                           (style['alpha'] if style['alpha'] is not None
+                            else 1.)).astype(np.uint8)
+        img = Image(imdata)
+        vb = self._mpl_ax_to(mplobj)
+        img.transform = STTransform.from_mapping([[0, 0], img.size],
+                                                 [[extent[0], extent[3]],
+                                                  [extent[1], extent[2]]])
+        img.parent = vb.scene
+
+    def draw_text(self, text, position, coordinates, style,
+                  text_type=None, mplobj=None):
+        _check_coords(coordinates, 'data')
+        color = Color(style['color'])
+        color.alpha = style['alpha']
+        color = color.rgba
+        text = Text(text, color=color, pos=position,
+                    font_size=style['fontsize'], rotation=style['rotation'],
+                    anchor_x=style['halign'], anchor_y=style['valign'])
+        text.parent = self._mpl_ax_to(mplobj).scene
+
+    def draw_markers(self, data, coordinates, style, label, mplobj=None):
+        _check_coords(coordinates, 'data')
+        edge_color = Color(style['edgecolor'])
+        edge_color.alpha = style['alpha']
+        face_color = Color(style['facecolor'])
+        face_color.alpha = style['alpha']
+        markers = Markers()
+        markers.set_data(data, face_color=face_color, edge_color=edge_color,
+                         size=style['markersize'], style=style['marker'])
+        markers.parent = self._mpl_ax_to(mplobj).scene
+
+    def draw_path(self, data, coordinates, pathcodes, style,
+                  offset=None, offset_coordinates="data", mplobj=None):
+        _check_coords(coordinates, 'data')
+        if offset is not None:
+            raise NotImplementedError('cannot handle offset')
+            _check_coords(offset_coordinates, 'data')
+        # TODO --, :, etc.
+        color = Color(style['edgecolor'])
+        color.alpha = style['alpha']
+        line = Line(data, color=color, width=style['edgewidth'],
+                    mode='gl')  # XXX Looks bad with agg :(
+        line.parent = self._mpl_ax_to(mplobj).scene
+
+    def _mpl_ax_to(self, mplobj, output='vb'):
+        """Helper to get the parent axes of a given mplobj"""
+        for ax in self._axs.values():
+            if ax['ax'] is mplobj.axes:
+                return ax[output]
+        raise RuntimeError('Parent axes could not be found!')
+
+    def _vispy_done(self):
+        """Things to do once all objects have been collected"""
+        self._resize(*self.canvas.size)
+
+    # def draw_path_collection(...) TODO add this for efficiency
+
+# https://github.com/mpld3/mplexporter/blob/master/
+#                    mplexporter/renderers/base.py
+
+
+def _mpl_to_vispy(fig):
+    """Convert a given matplotlib figure to vispy
+
+    This function is experimental and subject to change!
+    Requires matplotlib and mplexporter.
+
+    Parameters
+    ----------
+    fig : instance of matplotlib Figure
+        The populated figure to display.
+
+    Returns
+    -------
+    canvas : instance of Canvas
+        The resulting vispy Canvas.
+    """
+    renderer = VispyRenderer()
+    exporter = Exporter(renderer)
+    with warnings.catch_warnings(record=True):  # py3k mpl warning
+        exporter.run(fig)
+    renderer._vispy_done()
+    return renderer.canvas
+
+
+def show(block=False):
+    """Show current figures using vispy
+
+    Parameters
+    ----------
+    block : bool
+        If True, blocking mode will be used. If False, then non-blocking
+        / interactive mode will be used.
+
+    Returns
+    -------
+    canvases : list
+        List of the vispy canvases that were created.
+    """
+    if not has_matplotlib():
+        raise ImportError('Requires matplotlib version >= 1.2')
+    cs = [_mpl_to_vispy(plt.figure(ii)) for ii in plt.get_fignums()]
+    if block and len(cs) > 0:
+        cs[0].app.run()
+    return cs
diff --git a/vispy/shaders/__init__.py b/vispy/mpl_plot/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/mpl_plot/tests/__init__.py
diff --git a/vispy/mpl_plot/tests/test_show_vispy.py b/vispy/mpl_plot/tests/test_show_vispy.py
new file mode 100644
index 0000000..05d954e
--- /dev/null
+++ b/vispy/mpl_plot/tests/test_show_vispy.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+from nose.tools import assert_raises
+
+from vispy.io import read_png, load_data_file
+from vispy.testing import has_matplotlib, requires_application
+import vispy.mpl_plot as plt
+
+
+ at requires_application()
+def test_show_vispy():
+    """Some basic tests of show_vispy"""
+    if has_matplotlib():
+        n = 200
+        t = np.arange(n)
+        noise = np.random.RandomState(0).randn(n)
+        # Need, image, markers, line, axes, figure
+        plt.figure()
+        ax = plt.subplot(211)
+        ax.imshow(read_png(load_data_file('pyplot/logo.png')))
+        ax = plt.subplot(212)
+        ax.plot(t, noise, 'ko-')
+        plt.draw()
+        canvases = plt.show()
+        canvases[0].close()
+    else:
+        assert_raises(ImportError, plt.show)
diff --git a/vispy/plot/__init__.py b/vispy/plot/__init__.py
new file mode 100644
index 0000000..2a8e2ec
--- /dev/null
+++ b/vispy/plot/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+[Experimental] This module provides functions for displaying data from a
+command-line interface.
+"""
+
+__all__ = ['plot', 'image']
+
+from .plot import plot, image
diff --git a/vispy/plot/plot.py b/vispy/plot/plot.py
new file mode 100644
index 0000000..7348270
--- /dev/null
+++ b/vispy/plot/plot.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from ..scene import SceneCanvas, visuals
+
+plots = []
+
+
+def plot(*args, **kwds):
+    """ Create a new canvas and plot the given data. 
+    
+    For arguments, see scene.visuals.LinePlot.
+    """
+    canvas = SceneCanvas(keys='interactive')
+    canvas.view = canvas.central_widget.add_view()
+    line = visuals.LinePlot(*args, **kwds)
+    canvas.view.add(line)
+    canvas.view.camera.auto_zoom(line)
+    canvas.show()
+    plots.append(canvas)
+    return canvas
+
+
+def image(*args, **kwds):
+    """ Create a new canvas and display the given image data.
+    
+    For arguments, see scene.visuals.Image.
+    """
+    canvas = SceneCanvas(keys='interactive')
+    canvas.view = canvas.central_widget.add_view()
+    image = visuals.Image(*args, **kwds)
+    canvas.view.add(image)
+    canvas.show()
+    canvas.view.camera.auto_zoom(image)
+    plots.append(canvas)
+    return canvas
diff --git a/vispy/scene/__init__.py b/vispy/scene/__init__.py
new file mode 100644
index 0000000..1f47ad6
--- /dev/null
+++ b/vispy/scene/__init__.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+The vispy.scene namespace provides functionality for higher level
+visuals as well as scenegraph and related classes.
+
+
+Terminology
+-----------
+
+* **entity** - an object that lives in the scenegraph. It can have zero or
+  more children and zero or more parents (although one is recommended).
+  It also has a transform that maps the local coordinate frame to the
+  coordinate frame of the parent.
+
+* **scene** - a complete connected graph of entities.
+
+* **subscene** - the entities that are children of a viewbox. Any viewboxes
+  inside this subscene are part of the subscene, but not their children.
+  The SubScene class is the toplevel entity for any subscene. Each
+  subscene has its own camera, lights, aspect ratio, etc.
+
+* **visual** - an entity that has a visual representation. It can be made
+  visible/invisible and also has certain bounds.
+
+* **widget** - an entity of a certain size that provides interaction. It
+  is made to live in a 2D scene with a pixel camera.
+
+* **viewbox** - an entity that provides a rectangular window to which a
+  subscene is rendered. Clipping is performed in one of several ways.
+
+* **camera** - an entity that specifies how the subscene of a viewbox is
+  rendered to the pixel grid. It determines position and orientation
+  (through its transform) an projection (through a special
+  transformation property). Some cameras also provide interaction (e.g.
+  zooming). Although there can be multiple cameras in a subscene, each
+  subscene has one active camera.
+
+* **viewport** - as in glViewPort, a sub pixel grid in a framebuffer.
+
+* **drawing system** - a part of the viewbox that takes care of rendering
+  a subscene to the pixel grid of that viewbox.
+
+"""
+
+__all__ = ['SceneCanvas', 'Entity']
+
+from .entity import Entity  # noqa
+from .canvas import SceneCanvas  # noqa
+from . import visuals  # noqa
+from . import widgets  # noqa
+from . import cameras  # noqa
+from .visuals import *  # noqa
+from .cameras import *  # noqa
+from .transforms import *  # noqa
+from .widgets import *  # noqa
diff --git a/vispy/scene/cameras.py b/vispy/scene/cameras.py
new file mode 100644
index 0000000..a76a23f
--- /dev/null
+++ b/vispy/scene/cameras.py
@@ -0,0 +1,595 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+A brief explanation of how cameras work 
+---------------------------------------
+
+A Camera is responsible for setting the transform of a SubScene object such 
+that a certain part of the scene is mapped to the bounding rectangle of the 
+ViewBox. 
+
+The view of a camera is determined by its transform (that it has as
+being an entity) and its projection. The former is essentially the
+position and orientation of the camera, the latter determines field of
+view and any non-linear transform (such as perspective).
+
+"""
+from __future__ import division
+
+import numpy as np
+
+from .entity import Entity
+from ..geometry import Rect
+from .transforms import (STTransform, PerspectiveTransform, NullTransform,
+                         AffineTransform)
+
+
+def make_camera(cam_type, *args, **kwds):
+    """ Factory function for creating new cameras. 
+    
+    Parameters
+    ----------
+    cam_type : str
+        May be one of:
+            * 'panzoom' : Creates :class:`PanZoomCamera`
+            * 'turntable' : Creates :class:`TurntableCamera`
+            * None : Creates :class:`Camera`
+
+    All extra arguments are passed to the __init__ method of the selected
+    Camera class.
+    """
+    cam_types = {
+        None: BaseCamera,
+        'panzoom': PanZoomCamera,
+        'turntable': TurntableCamera,
+    }
+    
+    try: 
+        return cam_types[cam_type](*args, **kwds)
+    except KeyError:
+        raise KeyError('Unknown camera type "%s". Options are: %s' % 
+                       (cam_type, cam_types.keys()))
+
+
+class BaseCamera(Entity):
+    """ Camera describes the perspective from which a ViewBox views its 
+    subscene, and the way that user interaction affects that perspective.
+    
+    Most functionality is implemented in subclasses. This base class has
+    no user interaction and causes the subscene to use the same coordinate
+    system as the ViewBox.
+
+    Parameters
+    ----------
+    parent : Entity
+        The parent of the camera.
+    name : str
+        Name used to identify the camera in the scene.
+    """
+    def __init__(self, **kwargs):
+        self._viewbox = None
+        self._interactive = True
+        super(BaseCamera, self).__init__(**kwargs)
+        self.transform = NullTransform()
+
+    @property
+    def interactive(self):
+        """ Boolean describing whether the camera should enable or disable
+        user interaction.
+        """
+        return self._interactive
+    
+    @interactive.setter
+    def interactive(self, b):
+        self._interactive = b
+
+    @property
+    def viewbox(self):
+        """ The ViewBox that this Camera is attached to.        
+        """
+        return self._viewbox
+    
+    @viewbox.setter
+    def viewbox(self, vb):
+        if self._viewbox is not None:
+            self.disconnect()
+        self._viewbox = vb
+        if self._viewbox is not None:
+            self.connect()
+            self.parent = vb.scene
+        self._update_transform()
+    
+    def connect(self):
+        self._viewbox.events.mouse_press.connect(self.view_mouse_event)
+        self._viewbox.events.mouse_release.connect(self.view_mouse_event)
+        self._viewbox.events.mouse_move.connect(self.view_mouse_event)
+        self._viewbox.events.mouse_wheel.connect(self.view_mouse_event)
+        self._viewbox.events.resize.connect(self.view_resize_event)
+    
+    def disconnect(self):
+        self._viewbox.events.mouse_press.disconnect(self.view_mouse_event)
+        self._viewbox.events.mouse_release.disconnect(self.view_mouse_event)
+        self._viewbox.events.mouse_move.disconnect(self.view_mouse_event)
+        self._viewbox.events.mouse_wheel.disconnect(self.view_mouse_event)
+        self._viewbox.events.resize.disconnect(self.view_resize_event)
+    
+    def view_mouse_event(self, event):
+        """
+        The ViewBox received a mouse event; update transform 
+        accordingly.
+        """
+        pass
+        
+    def view_resize_event(self, event):
+        """
+        The ViewBox was resized; update the transform accordingly.
+        """
+        pass
+    
+    def _update_transform(self):
+        """ Subclasses should reimplement this method to update the scene
+        transform by calling self._set_scene_transform.
+        """
+        self._set_scene_transform(self.transform)
+        
+    def _set_scene_transform(self, tr):
+        """ Called by subclasses to configure the viewbox scene transform.
+        """
+        # todo: check whether transform has changed, connect to 
+        # transform.changed event
+        self._scene_transform = tr
+        if self.viewbox is not None:
+            self.viewbox.scene.transform = self._scene_transform
+            self.viewbox.update()
+
+    
+class PanZoomCamera(BaseCamera):
+    """
+    Camera implementing 2D pan/zoom mouse interaction. Primarily intended for
+    displaying plot data.
+
+    By default, this camera inverts the y axis of the scene. This usually 
+    results in the scene +y axis pointing upward because widgets (including 
+    ViewBox) have their +y axis pointing downward.
+    
+    User interaction:
+    
+    * Dragging left mouse button pans the view
+    * Dragging right mouse button vertically zooms the view y-axis
+    * Dragging right mouse button horizontally zooms the view x-axis
+    * Mouse wheel zooms both view axes equally.
+
+    Parameters
+    ----------
+    parent : Entity
+        The parent of the camera.
+    name : str
+        Name used to identify the camera in the scene.
+    """
+    def __init__(self, **kwargs):
+        super(PanZoomCamera, self).__init__(**kwargs)
+        self._rect = Rect((0, 0), (1, 1))  # visible range in scene
+        self._invert = [False, True]
+        self.transform = STTransform()
+        
+    def zoom(self, zoom, center):
+        """ Zoom the view around a center point.
+        
+        Parameters
+        ----------
+        zoom : length-2 sequence
+            The fraction to zoom the x and y axes.
+        center : length-2 sequence
+            The point (in the coordinate system of the scene) that will remain
+            stationary in the ViewBox while zooming.
+        """
+        # TODO: would be nice if STTransform had a nice scale(s, center) 
+        # method like AffineTransform.
+        transform = (STTransform(translate=center) * 
+                     STTransform(scale=zoom) * 
+                     STTransform(translate=-center))
+        
+        self.rect = transform.map(self.rect)
+        
+    def pan(self, pan):
+        """ Pan the view.
+        
+        Parameters
+        ----------
+        pan : length-2 sequence
+            The distance to pan the view, in the coordinate system of the 
+            scene.
+        """
+        self.rect = self.rect + pan
+
+    def auto_zoom(self, visual=None, padding=0.1):
+        """ Automatically configure the camera to fit a visual inside the
+        visible region.
+        """
+        bx = visual.bounds('visual', 0)
+        by = visual.bounds('visual', 1)
+        bounds = self.rect
+        if bx is not None:
+            bounds.left = bx[0]
+            bounds.right = bx[1]
+        if by is not None:
+            bounds.bottom = by[0]
+            bounds.top = by[1]
+            
+        if padding != 0:
+            pw = bounds.width * padding * 0.5
+            ph = bounds.height * padding * 0.5
+            bounds.left = bounds.left - pw
+            bounds.right = bounds.right + pw
+            bounds.top = bounds.top + ph
+            bounds.bottom = bounds.bottom - ph
+        self.rect = bounds
+
+    @property
+    def rect(self):
+        """ The rectangular border of the ViewBox visible area, expressed in
+        the coordinate system of the scene.
+        
+        By definition, the +y axis of this rect is opposite the +y axis of the
+        ViewBox. 
+        """
+        return self._rect
+        
+    @rect.setter
+    def rect(self, args):
+        """
+        Set the bounding rect of the visible area in the subscene. 
+        
+        By definition, the +y axis of this rect is opposite the +y axis of the
+        ViewBox. 
+        """
+        if isinstance(args, tuple):
+            self._rect = Rect(*args)
+        else:
+            self._rect = Rect(args)
+        self._update_transform()
+
+    @property 
+    def invert_y(self):
+        """ Boolean indicating whether the y axis of the SubScene is inverted 
+        relative to the ViewBox.
+        
+        Default is True--this camera inverts the y axis of the scene. In most
+        cases, this results in the scene +y axis pointing upward because 
+        widgets (including ViewBox) have their +y axis pointing downward.
+        """
+        return self._invert[1]
+    
+    @invert_y.setter
+    def invert_y(self, inv):
+        if not isinstance(inv, bool):
+            raise TypeError("Invert must be boolean.")
+        self._invert[1] = inv
+        self._update_transform()
+        
+    def view_resize_event(self, event):
+        self._update_transform()
+
+    def view_mouse_event(self, event):
+        """
+        The SubScene received a mouse event; update transform 
+        accordingly.
+        """
+        if event.handled or not self.interactive:
+            return
+        
+        if event.type == 'mouse_wheel':
+            scale = 1.1 ** -event.delta[1]
+            center = self._scene_transform.imap(event.pos[:2])
+            self.zoom((scale, scale), center)
+            event.handled = True
+            
+        elif event.type == 'mouse_move':
+            if 1 in event.buttons:
+                p1 = np.array(event.last_event.pos)[:2]
+                p2 = np.array(event.pos)[:2]
+                p1s = self._scene_transform.imap(p1)
+                p2s = self._scene_transform.imap(p2)
+                self.pan(p1s-p2s)
+                event.handled = True
+            elif 2 in event.buttons:
+                # todo: just access the original event position, rather
+                # than mapping to the viewbox and back again.
+                p1 = np.array(event.last_event.pos)[:2]
+                p2 = np.array(event.pos)[:2]
+                p1c = event.map_to_canvas(p1)[:2]
+                p2c = event.map_to_canvas(p2)[:2]
+                
+                scale = 1.03 ** ((p1c-p2c) * np.array([1, -1]))
+                center = self._scene_transform.imap(event.press_event.pos[:2])
+                
+                self.zoom(scale, center)
+                event.handled = True
+        
+        if event.handled:
+            self._update_transform()
+
+    def _update_transform(self):
+        if self.viewbox is None:
+            return
+        
+        vbr = self.viewbox.rect.flipped(x=self._invert[0], y=self._invert[1])
+        self.transform.set_mapping(self.rect, vbr)
+        self._set_scene_transform(self.transform)
+
+        
+class PerspectiveCamera(BaseCamera):
+    """ Base class for 3D cameras supporting orthographic and perspective
+    projections.
+    
+    User interaction:
+    
+    * Dragging left mouse button orbits the view around its center point.
+    * Mouse wheel changes the field of view angle.
+    
+    Parameters
+    ----------
+    mode : str
+        Perspective mode, either 'ortho' or 'perspective'.
+    fov : float
+        Field of view.
+    width : float
+        Width.
+    parent : Entity
+        The parent of the camera.
+    name : str
+        Name used to identify the camera in the scene.
+    """
+    def __init__(self, mode='ortho', fov=60., width=10., **kwargs):
+        # projection transform and associated options
+        self._projection = PerspectiveTransform()
+        self._mode = None
+        self._fov = None
+        self._width = None
+
+        super(PerspectiveCamera, self).__init__(**kwargs)
+
+        self.mode = mode
+        self.fov = fov
+        self.width = width
+        
+        # camera transform
+        self.transform = AffineTransform()
+        
+    @property
+    def mode(self):
+        """ Describes the current projection mode of the camera. 
+        
+        May be 'ortho' or 'perspective'. In orthographic mode, objects appear 
+        to have constant size regardless of their distance from the camera.
+        In perspective mode, objects appear smaller as they are farther from 
+        the camera.
+        """
+        return self._mode
+    
+    @mode.setter
+    def mode(self, mode):
+        if mode == 'ortho':
+            self._near = -1e3
+        elif mode == 'perspective':
+            self._near = 1e-2
+        else:
+            raise ValueError('Accepted modes are "ortho" and "perspective".')
+        self._far = 1e6
+        self._mode = mode
+        
+        self._update_transform()
+        
+    @property
+    def fov(self):
+        """ Field-of-view angle of the camera when in perspective mode.
+        """
+        return self._fov
+    
+    @fov.setter
+    def fov(self, fov):
+        if fov < 0 or fov >= 180:
+            raise ValueError("fov must be between 0 and 180.")
+        self._fov = fov
+        self._update_transform()
+        
+    @property
+    def width(self):
+        """ Width of the visible region when in orthographic mode.
+        """
+        return self._width
+    
+    @width.setter
+    def width(self, width):
+        self._width = width
+        self._update_transform()
+        
+    def view_resize_event(self, event):
+        self._update_transform()
+    
+    def _update_transform(self, event=None):
+        if self.viewbox is None:
+            return
+        
+        # configure projection transform
+        if self.mode == 'ortho': 
+            self.set_ortho()
+        elif self.mode == 'perspective':
+            self.set_perspective()
+        else:
+            raise ValueError("Unknown projection mode '%s'" % self.mode)
+        
+        # assemble complete transform mapping to viewbox bounds
+        unit = [[-1, 1], [1, -1]]
+        vrect = [[0, 0], self.viewbox.size]
+        viewbox_tr = STTransform.from_mapping(unit, vrect)
+        proj_tr = self._projection
+        cam_tr = self.entity_transform(self.viewbox.scene)
+        
+        tr = viewbox_tr * proj_tr * cam_tr
+        self._set_scene_transform(tr)
+
+    def set_perspective(self):
+        """ Set perspective projection matrix.
+        """
+        vbs = self.viewbox.size
+        ar = vbs[0] / vbs[1]
+        self._projection.set_perspective(self.fov, ar, self._near, self._far)
+
+    def set_ortho(self):
+        """ Set orthographic projection matrix.
+        """
+        vbs = self.viewbox.size
+        w = self.width / 2.
+        h = w * (vbs[1] / vbs[0])
+        self._projection.set_ortho(-w, w, -h, h, self._near, self._far)
+
+
+class TurntableCamera(PerspectiveCamera):
+    """ 3D camera class that orbits around a center point while maintaining a
+    fixed vertical orientation.
+
+    Parameters
+    ----------
+    elevation : float
+        Elevation in degrees.
+    azimuth : float
+        Azimuth in degrees.
+    distance : float
+        Distance away from the center.
+    center : array-like
+        3-element array defining the center point.
+    parent : Entity
+        The parent of the camera.
+    name : str
+        Name used to identify the camera in the scene.
+    """
+    def __init__(self, elevation=30., azimuth=30.,
+                 distance=10., center=(0, 0, 0), up='z', **kwds):
+        super(TurntableCamera, self).__init__(**kwds)
+        self.elevation = elevation
+        self.azimuth = azimuth
+        self.distance = distance
+        self.center = center
+        self.up = up
+        self._update_camera_pos()
+    
+    @property
+    def elevation(self):
+        """ The angle of the camera in degrees above the horizontal (x, z) 
+        plane.
+        """
+        return self._elevation
+
+    @elevation.setter
+    def elevation(self, elev):
+        self._elevation = elev
+        self._update_transform()
+    
+    @property
+    def azimuth(self):
+        """ The angle of the camera in degrees around the y axis. An angle of
+        0 places the camera within the (y, z) plane.
+        """
+        return self._azimuth
+
+    @azimuth.setter
+    def azimuth(self, azim):
+        self._azimuth = azim
+        self._update_transform()
+    
+    @property
+    def distance(self):
+        """ The distance from the camera to its center point.
+        """
+        return self._distance
+
+    @distance.setter
+    def distance(self, dist):
+        self._distance = dist
+        self._update_transform()
+    
+    @property
+    def center(self):
+        """ The position of the turntable center. This is the point around 
+        which the camera orbits.
+        """
+        return self._center
+
+    @center.setter
+    def center(self, center):
+        self._center = center
+        self._update_transform()
+    
+    def orbit(self, azim, elev):
+        """ Orbits the camera around the center position.
+        
+        Parameters
+        ----------
+        azim : float
+            Angle in degrees to rotate horizontally around the center point.
+        elev : float
+            Angle in degrees to rotate vertically around the center point.
+        """
+        self.azimuth += azim
+        self.elevation = np.clip(self.elevation + elev, -90, 90)
+        self._update_camera_pos()
+        
+    def view_mouse_event(self, event):
+        """
+        The viewbox received a mouse event; update transform 
+        accordingly.
+        """
+        if event.handled or not self.interactive:
+            return
+        
+        if event.type == 'mouse_wheel':
+            s = 1.1 ** -event.delta[1]
+            if self.mode == 'ortho':
+                self.width *= s
+            else:
+                self.fov = np.clip(self.fov * s, 0, 179)
+            self._update_camera_pos()
+        
+        elif event.type == 'mouse_move' and 1 in event.buttons:
+            p1 = np.array(event.last_event.pos)[:2]
+            p2 = np.array(event.pos)[:2]
+            p1c = event.map_to_canvas(p1)[:2]
+            p2c = event.map_to_canvas(p2)[:2]
+            d = p2c - p1c
+            self.orbit(-d[0], d[1])
+
+    def _update_camera_pos(self):
+        """ Set the camera position / orientation based on elevation,
+        azimuth, distance, and center properties.
+        """
+        # transform will be updated several times; do not update camera
+        # transform until we are done.
+        ch_em = self.events.transform_change
+        with ch_em.blocker(self._update_transform):
+            tr = self.transform
+            tr.reset()
+            if self.up == 'y':
+                tr.translate((0.0, 0.0, -self.distance))
+                tr.rotate(self.elevation, (-1, 0, 0))
+                tr.rotate(self.azimuth, (0, 1, 0))
+            elif self.up == 'z':
+                tr.rotate(90, (1, 0, 0))
+                tr.translate((0.0, -self.distance, 0.0))
+                tr.rotate(self.elevation, (-1, 0, 0))
+                tr.rotate(self.azimuth, (0, 0, 1))
+            else:
+                raise ValueError('TurntableCamera.up must be "y" or "z".')
+                
+            tr.translate(-np.array(self.center))
+        self._update_transform()
+
+
+#class ArcballCamera(PerspectiveCamera):
+#    pass
+
+
+#class FirstPersonCamera(PerspectiveCamera):
+#    pass
diff --git a/vispy/scene/canvas.py b/vispy/scene/canvas.py
new file mode 100644
index 0000000..1f0af0f
--- /dev/null
+++ b/vispy/scene/canvas.py
@@ -0,0 +1,338 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import weakref
+
+from .. import gloo
+from .. import app
+from .subscene import SubScene
+from .entity import Entity
+from .transforms import STTransform, TransformCache
+from .events import SceneDrawEvent, SceneMouseEvent
+from ..color import Color
+from ..util import logger
+from .widgets import Widget
+
+
+class SceneCanvas(app.Canvas):
+    """ SceneCanvas provides a Canvas that automatically draws the contents
+    of a scene.
+
+    Receives the following events:
+    initialize, resize, draw, mouse_press, mouse_release, mouse_move,
+    mouse_wheel, key_press, key_release, stylus, touch, close
+
+    Parameters
+    ----------
+    title : str
+        The widget title
+    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 draw event.
+        Default True. If True, the ``swap_buffers`` Canvas method will
+        be called last (by default) by the ``canvas.draw`` event handler.
+    app : Application | str
+        Give vispy Application instance to use as a backend.
+        (vispy.app is used by default.) If str, then an application
+        using the chosen backend (e.g., 'pyglet') will be created.
+        Note the canvas application can be accessed at ``canvas.app``.
+    create_native : bool
+        Whether to create the widget immediately. Default True.
+    init_gloo : bool
+        Initialize standard values in gloo (e.g., ``GL_POINT_SPRITE``).
+    vsync : bool
+        Enable vertical synchronization.
+    resizable : bool
+        Allow the window to be resized.
+    decorate : bool
+        Decorate the window.
+    fullscreen : bool | int
+        If False, windowed mode is used (default). If True, the default
+        monitor is used. If int, the given monitor number is used.
+    context : dict | instance SharedContext | None
+        OpenGL configuration to use when creating the context for the canvas,
+        or a context to share. If None, ``vispy.app.get_default_config`` will
+        be used to set the OpenGL context parameters. Alternatively, the
+        ``canvas.context`` property from an existing canvas (using the
+        same backend) will return a ``SharedContext`` that can be used,
+        thereby sharing the existing context.
+    keys : str | dict | None
+        Default key mapping to use. If 'interactive', escape and F11 will
+        close the canvas and toggle full-screen mode, respectively.
+        If dict, maps keys to functions. If dict values are strings,
+        they are assumed to be ``Canvas`` methods, otherwise they should
+        be callable.
+    parent : widget-object
+        The parent widget if this makes sense for the used backend.
+    bgcolor : Color
+        The background color to use.
+
+    See also
+    --------
+    vispy.app.Canvas
+    """
+    def __init__(self, *args, **kwargs):
+        self._fb_stack = []  # for storing information about framebuffers used
+        self._vp_stack = []  # for storing information about viewports used
+        self._scene = None
+        self._bgcolor = Color(kwargs.pop('bgcolor', 'black')).rgba
+        
+        # A default widget that follows the shape of the canvas
+        self._central_widget = None
+
+        app.Canvas.__init__(self, *args, **kwargs)
+        self.events.mouse_press.connect(self._process_mouse_event)
+        self.events.mouse_move.connect(self._process_mouse_event)
+        self.events.mouse_release.connect(self._process_mouse_event)
+        self.events.mouse_wheel.connect(self._process_mouse_event)
+
+        # Collection of transform caches; one for each root visual used in 
+        # self.draw_visual(...)
+        self._transform_caches = weakref.WeakKeyDictionary()
+
+        # Set up default entity stack: ndc -> fb -> canvas -> scene
+        self.render_cs = Entity()
+        self.framebuffer_cs = Entity(parent=self.render_cs)
+        self.framebuffer_cs.transform = STTransform()
+        self.canvas_cs = Entity(parent=self.framebuffer_cs)
+        self.canvas_cs.transform = STTransform()
+        # By default, the document coordinate system is the canvas.
+        self.canvas_cs.document = self.canvas_cs
+        
+        self.scene = SubScene(parent=self.canvas_cs)
+        
+    @property
+    def scene(self):
+        """ The SubScene object that represents the root entity of the
+        scene graph to be displayed.
+        """
+        return self._scene
+
+    @scene.setter
+    def scene(self, e):
+        if self._scene is not None:
+            self._scene.events.update.disconnect(self._scene_update)
+        self._scene = e
+        self._scene.events.update.connect(self._scene_update)
+
+    @property
+    def central_widget(self):
+        """ Returns the default widget that occupies the entire area of the
+        canvas. 
+        """
+        if self._central_widget is None:
+            self._central_widget = Widget(size=self.size, parent=self.scene)
+        return self._central_widget
+
+    def _scene_update(self, event):
+        self.update()
+
+    def on_draw(self, event):
+        gloo.clear(color=self._bgcolor, depth=True)
+        if self._scene is None:
+            return  # Can happen on initialization
+        logger.debug('Canvas draw')
+        
+        # Draw the scene, but first disconnect its change signal--
+        # any changes that take place during the paint should not trigger
+        # a subsequent repaint.
+        with self.scene.events.update.blocker(self._scene_update):
+            self.draw_visual(self.scene)
+        
+        if len(self._vp_stack) > 0:
+            logger.warning("Viewport stack not fully cleared after draw.")
+        if len(self._fb_stack) > 0:
+            logger.warning("Framebuffer stack not fully cleared after draw.")
+
+    def draw_visual(self, visual, event=None):
+        """ Draw a *visual* and its children on the canvas.
+        """
+        # Create draw event, which keeps track of the path of transforms
+        self._process_entity_count = 0  # for debugging
+        
+        # Get the cache of transforms used for this visual
+        tr_cache = self._transform_caches.setdefault(visual, TransformCache())
+        # and mark the entire cache as aged
+        tr_cache.roll()
+        
+        scene_event = SceneDrawEvent(canvas=self, event=event, 
+                                     transform_cache=tr_cache)
+        scene_event.push_viewport((0, 0) + self.size)
+        try:
+            # Force update of transforms on base entities
+            # TODO: this should happen as a reaction to resize, push_viewport,
+            #       etc.; not here.  (but note the transforms must change
+            #       following push_viewport)
+            self.fb_ndc_transform
+            self.canvas_fb_transform
+            
+            scene_event.push_entity(self.render_cs)
+            scene_event.push_entity(self.framebuffer_cs)
+            scene_event.push_entity(self.canvas_cs)
+            scene_event.push_entity(visual)
+            visual.draw(scene_event)
+        finally:
+            scene_event.pop_viewport()
+
+    def _process_mouse_event(self, event):
+        tr_cache = self._transform_caches.setdefault(self.scene, 
+                                                     TransformCache())
+        scene_event = SceneMouseEvent(canvas=self, event=event,
+                                      transform_cache=tr_cache)
+        scene_event.push_entity(self.render_cs)
+        scene_event.push_entity(self.framebuffer_cs)
+        scene_event.push_entity(self.canvas_cs)
+        scene_event.push_entity(self._scene)
+        self._scene._process_mouse_event(scene_event)
+        
+        # If something in the scene handled the scene_event, then we mark
+        # the original event accordingly.
+        event.handled = scene_event.handled
+
+    def on_resize(self, event):
+        if self._central_widget is not None:
+            self._central_widget.size = self.size
+
+    # -------------------------------------------------- transform handling ---
+    def push_viewport(self, viewport):
+        """ Push a viewport (x, y, w, h) on the stack. It is the
+        responsibility of the caller to ensure the given values are
+        int. The viewport's origin is defined relative to the current
+        viewport.
+        """
+        vp = list(viewport)
+        # Normalize viewport before setting;
+        if vp[2] < 0:
+            vp[0] += vp[2]
+            vp[2] *= -1
+        if vp[3] < 0:
+            vp[1] += vp[3]
+            vp[3] *= -1
+            
+        self._vp_stack.append(vp)
+        self.fb_ndc_transform  # update!
+        # Apply
+        try:
+            self._set_viewport(vp)
+        except:
+            self._vp_stack.pop()
+            self.fb_ndc_transform  # update!
+            raise
+
+    def pop_viewport(self):
+        """ Pop a viewport from the stack.
+        """
+        vp = self._vp_stack.pop()
+        # Activate latest
+        if len(self._vp_stack) > 0:
+            self._set_viewport(self._vp_stack[-1])
+            self.fb_ndc_transform  # update!
+        return vp
+    
+    def _set_viewport(self, vp):
+        from .. import gloo
+        gloo.set_viewport(*vp)
+
+    def push_fbo(self, fbo, offset, csize):
+        """ Push an FBO on the stack, together with the new viewport.
+        and the transform to the FBO.
+        """
+        self._fb_stack.append((fbo, offset, csize))
+        self.canvas_fb_transform  # update!
+        
+        # Apply
+        try:
+            fbo.activate()
+            h, w = fbo.color_buffer.shape[:2]
+            self.push_viewport((0, 0, w, h))
+        except Exception:
+            self._fb_stack.pop()
+            raise
+
+    def pop_fbo(self):
+        """ Pop an FBO from the stack.
+        """
+        fbo = self._fb_stack.pop()
+        fbo[0].deactivate()
+        self.pop_viewport()
+        if len(self._fb_stack) > 0:
+            old_fbo = self._fb_stack[-1]
+            old_fbo[0].activate()
+        self.canvas_fb_transform  # update!
+        return fbo
+        
+    def _current_framebuffer(self):
+        """ Return (fbo, origin, canvas_size) for the current
+        FBO on the stack, or for the canvas if there is no FBO.
+        """
+        if len(self._fb_stack) == 0:
+            return None, (0, 0), self.size
+        else:
+            return self._fb_stack[-1]
+
+    @property
+    def canvas_fb_transform(self):
+        """ The transform that maps from the canvas coordinate system to the
+        current framebuffer coordinate system. 
+        
+        The framebuffer coordinate 
+        system is used for antialiasing calculations, and is also the 
+        system used when specifying coordinates for glViewport 
+        (or gloo.set_viewport). Its origin is in the lower-left corner (as
+        opposed to the document / canvas coordinate system, which has its
+        origin in the upper-left corner).
+        
+        Often the canvas and framebuffer coordinate systems are identical. 
+        However, some systems with high-resolution 
+        displays may use framebuffers with higher resolution than the reported
+        size of the canvas. Likewise, when rendering to an FBO, the resolution
+        and offset of the framebuffer may not match the canvas. 
+        """
+        fbo, offset, csize = self._current_framebuffer()
+        if fbo is None:
+            # todo: account for high-res displays here.
+            fbsize = csize
+        else:
+            fbsize = fbo.color_buffer.shape
+            # image shape is (rows, cols), unlike canvas shape.
+            fbsize = fbsize[1], fbsize[0]  
+
+        map_from = [list(offset), [offset[0] + csize[0], offset[1] + csize[1]]]
+        map_to = [[0, fbsize[1]], [fbsize[0], 0]]
+        
+        self.canvas_cs.transform.set_mapping(map_from, map_to)
+        return self.canvas_cs.transform
+
+    @property
+    def fb_ndc_transform(self):
+        """ The transform that maps from the framebuffer coordinate system to
+        normalized device coordinates (which is the obligatory output 
+        coordinate system for all vertex shaders). This transform accounts for
+        the current glViewport.
+        """
+        offset, csize, fbsize = self._current_framebuffer()
+        x, y, w, h = self._vp_stack[-1]
+        
+        map_from = [[x, y], [x+w, y+h]]
+        map_to = [[-1, -1], [1, 1]]
+        
+        self.framebuffer_cs.transform.set_mapping(map_from, map_to)
+        return self.framebuffer_cs.transform
+    
+    @property
+    def render_transform(self):
+        """ The transform that maps from the Canvas pixel coordinate system 
+        <(0, 0) at top-left, (w, h) at bottom-right> to normalized device 
+        coordinates within the current glViewport and FBO.
+
+        Most visuals should use this transform when drawing.
+        """
+        return self.fb_ndc_transform * self.canvas_fb_transform
diff --git a/vispy/scene/components/__init__.py b/vispy/scene/components/__init__.py
new file mode 100644
index 0000000..a613f3f
--- /dev/null
+++ b/vispy/scene/components/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from .color import UniformColorComponent, VertexColorComponent  # noqa
+from .component import VisualComponent  # noqa
+from .material import GridContourComponent, ShadingComponent  # noqa
+from .normal import VertexNormalComponent  # noqa
+from .texture import (TextureComponent, VertexTextureCoordinateComponent,  # noqa
+                      TextureCoordinateComponent)  # noqa
+from .vertex import XYPosComponent, XYZPosComponent, HeightFieldComponent  # noqa
diff --git a/vispy/scene/components/color.py b/vispy/scene/components/color.py
new file mode 100644
index 0000000..e2d6e1e
--- /dev/null
+++ b/vispy/scene/components/color.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Color components are modular shader components used for retrieving or
+generating fragment colors.
+
+These components create a function in the fragment shader that accepts no
+arguments and returns a vec4 color.
+"""
+
+from __future__ import division
+
+import numpy as np
+
+from .component import VisualComponent
+from ..shaders import Varying
+from ... import gloo
+
+
+class UniformColorComponent(VisualComponent):
+    """
+    Generates a uniform color for all vertices.
+    """
+
+    SHADERS = dict(
+        frag_color="""
+            vec4 colorInput() {
+                return $rgba;
+            }
+        """)
+
+    def __init__(self, color=(1, 1, 1, 1)):
+        super(UniformColorComponent, self).__init__()
+        self._color = color
+
+    @property
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, c):
+        self._color = c
+
+    def activate(self, program, mode):
+        self._funcs['frag_color']['rgba'] = np.array(self._color)
+
+
+class VertexColorComponent(VisualComponent):
+    """
+    Reads color in from (N,4) array or vertex buffer.
+    """
+
+    SHADERS = dict(
+        frag_color="""
+            vec4 colorInput() {
+                return $rgba;
+            }
+        """,
+        vert_post_hook="""
+            void colorInputSupport() {
+                $output_color = $input_color;
+            }
+        """)
+
+    def __init__(self, color=None):
+        super(VertexColorComponent, self).__init__()
+        self._color = color
+        self._vbo = None
+
+        # Create Varying to connect vertex / fragment shaders
+        var = Varying('rgba', dtype='vec4')
+        self._funcs['frag_color']['rgba'] = var
+        self._funcs['vert_post_hook']['output_color'] = var
+
+    @property
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, c):
+        self._color = c
+
+    @property
+    def vbo(self):
+        if self._vbo is None:
+            self._vbo = gloo.VertexBuffer(self._color.astype(np.float32))
+        return self._vbo
+
+    def activate(self, program, mode):
+        vf = self._funcs['vert_post_hook']
+        vf['input_color'] = self.vbo
diff --git a/vispy/scene/components/component.py b/vispy/scene/components/component.py
new file mode 100644
index 0000000..f9d7ee7
--- /dev/null
+++ b/vispy/scene/components/component.py
@@ -0,0 +1,136 @@
+from ..shaders import Function
+
+
+class VisualComponent(object):
+    """
+    Base for classes that encapsulate some modular component of a Visual.
+
+    These define Functions for extending the shader code as well as an
+    activate() method that inserts these Functions into a program.
+
+    VisualComponents may be considered friends of the Visual they are attached
+    to; often they will need to access internal data structures of the Visual
+    to make decisions about constructing shader components.
+    """
+
+    DRAW_PRE_INDEXED = 1
+    DRAW_UNINDEXED = 2
+
+    # Maps {'program_hook': 'GLSL code'}
+    SHADERS = {}
+
+    # List of shaders to automatically attach to the visual's program.
+    # If None, then all shaders are attached.
+    AUTO_ATTACH = None
+
+    def __init__(self, visual=None):
+        self._visual = None
+        if visual is not None:
+            self._attach(visual)
+
+        self._funcs = dict([(name, Function(code))
+                            for name, code in self.SHADERS.items()])
+
+        # components that are required by this component
+        self._deps = []
+
+        # only detach when count drops to 0;
+        # this is like a reference count for component dependencies.
+        self._attach_count = 0
+
+    @property
+    def visual(self):
+        """The Visual that this component is attached to."""
+        return self._visual
+
+    def _attach(self, visual):
+        """Attach this component to a Visual. This should be called by the
+        Visual itself.
+
+        A component may only be attached to a single Visual. However, it may
+        be attached _multiple times_ to the same visual.
+
+        The default implementation of this method calls
+        self._auto_attach_shaders() to generate the list of shader callbacks
+        that should be added to the Visual's program.
+        """
+        if visual is not self._visual and self._visual is not None:
+            raise Exception("Cannot attach component %s to %s; already "
+                            "attached to %s" % (self, visual, self._visual))
+        self._visual = visual
+        self._attach_count += 1
+        for hook in self._auto_attach_shaders():
+            func = self._funcs[hook]
+            try:
+                visual._program.vert.add_callback(hook, func)
+            except KeyError:
+                visual._program.frag.add_callback(hook, func)
+                
+        for comp in self._deps:
+            comp._attach(visual)
+
+    def _detach(self):
+        """Detach this component from its Visual. This should be called by the
+        visual itself.
+
+        If the component was attached
+        multiple times, it must be detached the same number of times.
+        """
+        if self._attach_count == 0:
+            raise Exception("Cannot detach component %s; not attached." % self)
+
+        self._attach_count -= 1
+        if self._attach_count == 0:
+            for hook in self._auto_attach_shaders():
+                func = self._funcs[hook]
+                try:
+                    self._visual._program.vert.remove_callback(hook, func)
+                except KeyError:
+                    self._visual._program.frag.remove_callback(hook, func)
+            self._visual = None
+            for comp in self._deps:
+                comp._detach()
+
+    def _auto_attach_shaders(self):
+        """
+        Return a list of shaders to automatically attach/detach
+        """
+        if self.AUTO_ATTACH is None:
+            return self._funcs.keys()
+        else:
+            return self.AUTO_ATTACH
+
+    @property
+    def supported_draw_modes(self):
+        """
+        A set of the draw modes (either DRAW_PRE_INDEXED, DRAW_UNINDEXED, or
+        both) currently supported by this component.
+
+        DRAW_PRE_INDEXED indicates that the component may be used when the
+        program uses an array of indices to determine the order of elements to
+        draw from its vertex buffers (using glDrawElements).
+
+        DRAW_UNINDEXED indicates that the component may be used when the
+        program will not use an array of indices; rather, vertex buffers are
+        processed in the order they appear in the buffer (using glDrawArrays).
+
+        By default, this method returns a tuple with both values. Components
+        that only support one mode must override this method.
+        """
+        # TODO: This should be expanded to include other questions, such as
+        # whether the component supports geometry shaders.
+        return set([self.DRAW_PRE_INDEXED, self.DRAW_UNINDEXED])
+
+    def update(self):
+        """
+        Inform the attached visual that this component has changed.
+        """
+        if self.visual is not None:
+            self.visual.update()
+
+    def activate(self, program):
+        """
+        *program* is about to draw; attach to *program* all functions and
+        data required by this component.
+        """
+        raise NotImplementedError
diff --git a/vispy/scene/components/material.py b/vispy/scene/components/material.py
new file mode 100644
index 0000000..0b67aa0
--- /dev/null
+++ b/vispy/scene/components/material.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Material components are modular shader components used for modifying fragment
+colors to change the visual's appearance.
+
+These generally create a function in the fragment shader that accepts a vec4
+color as its only argument and returns a modified vec4 color.
+"""
+
+from __future__ import division
+
+from .component import VisualComponent
+from ..shaders import Varying
+
+
+class GridContourComponent(VisualComponent):
+    """
+    Draw grid lines across a surface.
+    """
+
+    SHADERS = dict(
+        frag_color="""
+            vec4 grid_contour(vec4 color) {
+                if ( mod($pos.x, $spacing.x) < 0.005 ||
+                    mod($pos.y, $spacing.y) < 0.005 ||
+                    mod($pos.z, $spacing.z) < 0.005 ) {
+                return color + 0.7 * (vec4(1,1,1,1) - color);
+                }
+                else {
+                    return color;
+                }
+            }
+        """,
+        vert_post_hook="""
+            void grid_contour_support() {
+                $output_pos = local_position();
+            }
+        """)
+
+    def __init__(self, spacing):
+        super(GridContourComponent, self).__init__()
+        self.spacing = spacing
+        
+        # Create Varying to connect vertex / fragment shaders
+        var = Varying('pos', dtype='vec4')
+        self._funcs['frag_color']['pos'] = var
+        self._funcs['vert_post_hook']['output_pos'] = var
+
+    @property
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, c):
+        self._color = c
+
+    def activate(self, program, mode):
+        ff = self._funcs['frag_color']
+        ff['spacing'] = self.spacing  # uniform vec3
+
+
+class ShadingComponent(VisualComponent):
+    """
+    Phong reflection and shading material.
+    """
+
+    SHADERS = dict(
+        frag_color="""
+            vec4 shading(vec4 color) {
+                vec3 norm = normalize($normal().xyz);
+                vec3 light = normalize($light_direction.xyz);
+                float p = dot(light, norm);
+                p = (p < 0. ? 0. : p);
+                vec4 diffuse = $light_color * p;
+                diffuse.a = 1.0;
+                p = dot(reflect(light, norm), vec3(0,0,1));
+                if (p < 0.0) {
+                    p = 0.0;
+                }
+                vec4 specular = $light_color * 5.0 * pow(p, 100.);
+                return color * ($ambient + diffuse) + specular;
+            }
+        """)
+
+    def __init__(self, normal_comp, lights, ambient=0.2):
+        super(ShadingComponent, self).__init__()
+        self.normal_comp = normal_comp
+        self._deps = [normal_comp]
+        self.lights = lights
+        self.ambient = ambient
+
+    def activate(self, program, mode):
+        # Normals are generated by output of another component
+        ff = self._funcs['frag_color']
+        ff['normal'] = self.normal_comp.normal_shader()
+
+        # TODO: add support for multiple lights
+        ff['light_direction'] = tuple(self.lights[0][0][:3]) + (1,)  # u vec4
+        ff['light_color'] = tuple(self.lights[0][1][:3]) + (1,)  # u vec4
+        ff['ambient'] = self.ambient  # u float
diff --git a/vispy/scene/components/normal.py b/vispy/scene/components/normal.py
new file mode 100644
index 0000000..14c61be
--- /dev/null
+++ b/vispy/scene/components/normal.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Normal components are modular shader components used for retrieving or
+generating surface normal vectors.
+
+These components generate a function in the fragment shader that accepts no
+arguments and returns a vec4 normal vector. Typically, the normal vector
+is computed in the vertex shader and passed by varying to the fragment
+shader.
+"""
+
+from __future__ import division
+
+from .component import VisualComponent
+from ..shaders import Varying
+from ... import gloo
+
+
+class VertexNormalComponent(VisualComponent):
+    SHADERS = dict(
+        frag_normal="""
+            vec4 normal() {
+                return $norm;
+            }
+        """,
+        vert_post_hook="""
+            void normal_support() {
+                //vec3 o = vec3(0,0,0);
+                //vec3 i = o + $input_normal.xyz;
+                //$output_normal = $map_local_to_nd(vec4(i,1)) -
+                //                 $map_local_to_nd(vec4(o,1));
+                $output_normal = vec4($input_normal, 1);
+            }
+        """)
+
+    # exclude frag_normal when auto-attaching shaders because the visual
+    # does not have a 'frag_normal' hook; instead this function will be called
+    # by another component.
+    AUTO_ATTACH = ['vert_post_hook']
+
+    def __init__(self, meshdata, smooth=True):
+        super(VertexNormalComponent, self).__init__()
+        self._meshdata = meshdata
+        self.smooth = smooth
+        self._vbo = None
+        self._vbo_mode = None
+
+        # Create Varying to connect vertex / fragment shaders
+        var = Varying('norm', dtype='vec4')
+        self._funcs['frag_normal']['norm'] = var
+        self._funcs['vert_post_hook']['output_normal'] = var
+
+    def _make_vbo(self, mode):
+        if self._vbo is None or self._vbo_mode != mode:
+            if mode is self.DRAW_PRE_INDEXED:
+                index = 'faces'
+            else:
+                index = None
+            if self.smooth:
+                norm = self._meshdata.vertex_normals(indexed=index)
+            else:
+                if index != 'faces':
+                    raise Exception("Not possible to draw faceted mesh without"
+                                    "pre-indexing.")
+                norm = self._meshdata.face_normals(indexed=index)
+            self._vbo = gloo.VertexBuffer(norm)
+            self._vbo_mode = mode
+        return self._vbo
+
+    def normal_shader(self):
+        """
+        Return the fragment shader function that returns a normal vector.
+        """
+        return self._funcs['frag_normal']
+
+    def activate(self, program, mode):
+        vf = self._funcs['vert_post_hook']
+        vf['input_normal'] = self._make_vbo(mode)  # attribute vec4
+        vf['map_local_to_nd'] = self.visual._program.vert['map_local_to_nd']
+
+    @property
+    def supported_draw_modes(self):
+        if self.smooth:
+            return set([self.DRAW_PRE_INDEXED, self.DRAW_UNINDEXED])
+        else:
+            # not possible to draw faceted mesh without pre-indexing.
+            return set([self.DRAW_PRE_INDEXED])
diff --git a/vispy/scene/components/texture.py b/vispy/scene/components/texture.py
new file mode 100644
index 0000000..f1606be
--- /dev/null
+++ b/vispy/scene/components/texture.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Normal components are modular shader components used for retrieving or
+generating surface normal vectors.
+
+These components generate a function in the fragment shader that accepts no
+arguments and returns a vec4 normal vector. Typically, the normal vector
+is computed in the vertex shader and passed by varying to the fragment
+shader.
+"""
+
+from __future__ import division
+
+from .component import VisualComponent
+from ..shaders import Varying
+from ... import gloo
+
+
+class TextureComponent(VisualComponent):
+    """
+    Component that reads a texture uniform.
+
+    A separate texture coordinate component must be provided.
+    If the texture coordinate is outside the edge of the texture, then
+    the fragment is discarded.
+    """
+
+    SHADERS = dict(
+        frag_color="""
+            vec4 texture_read() {
+                vec2 tex_coord = $texture_coordinate();
+                if(tex_coord.x < 0.0 || tex_coord.x > 1.0 ||
+                tex_coord.y < 0.0 || tex_coord.y > 1.0) {
+                    discard;
+                }
+                return texture2D($texture, tex_coord.xy);
+            }
+        """)
+
+    def __init__(self, texture, tex_coord_comp):
+        super(TextureComponent, self).__init__()
+        self.tex_coord_comp = tex_coord_comp
+        self.texture = texture
+        self._deps = [tex_coord_comp]
+
+    def activate(self, program, mode):
+        # Texture coordinates are generated by a separate component.
+        ff = self._funcs['frag_color']
+        ff['texture_coordinate'] = self.tex_coord_comp.coord_shader()
+        #ff['texture'] = ('uniform', 'sampler2D', self.texture)
+        ff['texture'] = self.texture
+
+
+class VertexTextureCoordinateComponent(VisualComponent):
+    """
+    Class that reads texture coordinates from a vertex buffer.
+    """
+    SHADERS = dict(
+        vert_post_hook="""
+            void texture_coord_support() {
+                $tex_local_pos = $local_pos;
+            }
+        """,
+        texture_coord="""
+            vec2 vertex_tex_coord() {
+                vec4 tex_coord = $map_local_to_tex($tex_local_pos);
+                return tex_coord.xy;
+            }
+        """)
+
+    # exclude texture_coord when auto-attaching shaders because the visual
+    # does not have a 'texture_coord' hook; instead this function will be
+    # called by another component.
+    AUTO_ATTACH = ['vert_post_hook']
+
+    def __init__(self, transform):
+        super(VertexTextureCoordinateComponent, self).__init__()
+        self.transform = transform
+        
+        # Create Varying to connect vertex / fragment shaders
+        var = Varying('v_tex_local_pos', dtype='vec4')
+        self.coord_shader()['tex_local_pos'] = var
+        self._funcs['vert_post_hook']['tex_local_pos'] = var
+
+    def coord_shader(self):
+        """
+        Return the fragment shader function that returns a texture coordinate.
+        """
+        return self._funcs['texture_coord']
+
+    def activate(self, program, mode):
+        ff = self.coord_shader()
+        ff['map_local_to_tex'] = self.transform.shader_map()
+        self._funcs['vert_post_hook']['local_pos'] = \
+            self.visual._program.vert['local_pos']
+
+
+class TextureCoordinateComponent(VisualComponent):
+    """
+    Component that outputs texture coordinates derived from the local vertex
+    coordinate and a transform.
+    """
+
+    SHADERS = dict(
+        vert_post_hook="""
+            void texture_coord_support() {
+                $tex_coord_output = $tex_coord;
+            }
+        """,
+        texture_coord="""
+            vec2 tex_coord() {
+                return $tex_coord_input;
+            }
+        """)
+
+    # exclude texture_coord when auto-attaching shaders because the visual
+    # does not have a 'texture_coord' hook; instead this function will be
+    # called by another component.
+    AUTO_ATTACH = ['vert_post_hook']
+
+    def __init__(self, coords):
+        super(TextureCoordinateComponent, self).__init__()
+        self.coords = coords
+        self._vbo = None
+        
+        # Create Varying to connect vertex / fragment shaders
+        var = Varying('v_tex_coord', dtype='vec2')
+        self.coord_shader()['tex_coord_input'] = var
+        self._funcs['vert_post_hook']['tex_coord_output'] = var
+
+    def coord_shader(self):
+        """
+        Return the fragment shader function that returns a texture coordinate.
+        """
+        return self._funcs['texture_coord']
+
+    @property
+    def vbo(self):
+        if self._vbo is None:
+            self._vbo = gloo.VertexBuffer(self.coords)
+        return self._vbo
+
+    def activate(self, program, mode):
+        vf = self._funcs['vert_post_hook']
+        #vf['tex_coord_output'] = Varying('v_tex_coord', dtype='vec2')
+        #self._funcs['texture_coord']['tex_coord_input'] = \
+        #    vf['tex_coord_output']
+        vf['tex_coord'] = self.vbo  # attribute vec2
diff --git a/vispy/scene/components/vertex.py b/vispy/scene/components/vertex.py
new file mode 100644
index 0000000..baa1ed7
--- /dev/null
+++ b/vispy/scene/components/vertex.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Vertex components are modular shader components used for retrieving or
+generating untransformed vertex locations.
+
+These components create a function in the vertex shader that accepts no
+arguments and returns a vec4 vertex location in the local coordinate system
+of the visual.
+"""
+
+from __future__ import division
+
+import numpy as np
+
+from .component import VisualComponent
+from ... import gloo
+
+
+class XYPosComponent(VisualComponent):
+    """
+    generate local coordinate from xy (vec2) attribute and z (float) uniform
+    """
+    SHADERS = dict(
+        local_position="""
+            vec4 input_xy_pos() {
+                return vec4($xy_pos, $z_pos, 1.0);
+            }
+        """)
+
+    def __init__(self, xy=None, z=0.0, index=None):
+        super(XYPosComponent, self).__init__()
+        self._xy = None
+        self._z = 0.0
+        self._index = False
+        self._vbo = None
+        self._ibo = None
+        self.set_data(xy[:, :2], z, index)
+
+    @property
+    def supported_draw_modes(self):
+        # TODO: Add support for converting between pre-indexed and unindexed
+        if self._index is False:
+            return set([self.DRAW_PRE_INDEXED])
+        else:
+            return set([self.DRAW_UNINDEXED])
+
+    def set_data(self, xy=None, z=None, index=None):
+        if xy is not None:
+            self._xy = xy
+        if z is not None:
+            self._z = z
+        if index is not None:
+            self._index = index
+        # TODO: might be better to re-upload data rather than creating
+        # a new VB, if possible.
+        self._vbo = None
+        self.update()
+
+    @property
+    def vbo(self):
+        if self._vbo is None:
+            self._vbo = gloo.VertexBuffer(self._xy)
+        return self._vbo
+
+    @property
+    def ibo(self):
+        if self._ibo is None:
+            self._ibo = gloo.IndexBuffer(self._index)
+        return self._ibo
+
+    def activate(self, program, draw_mode):
+        fn = self._funcs['local_position']
+        #fn['xy_pos'] = ('attribute', 'vec2', self.vbo)
+        #fn['z_pos'] = ('uniform', 'float', self._z)
+        fn['xy_pos'] = self.vbo
+        fn['z_pos'] = self._z
+
+    @property
+    def index(self):
+        if self._index is False:
+            return None
+        else:
+            return self.ibo
+
+
+class XYZPosComponent(VisualComponent):
+    """
+    generate local coordinate from xyz (vec3) attribute
+    """
+    SHADERS = dict(
+        local_position="""
+            vec4 input_xyz_pos() {
+                return vec4($xyz_pos, 1.0);
+            }
+        """)
+
+    def __init__(self, pos=None, index=None):
+        super(XYZPosComponent, self).__init__()
+        self._pos = None
+        self._index = False
+        self._vbo = None
+        self._ibo = None
+        self.set_data(pos, index)
+
+    @property
+    def supported_draw_modes(self):
+        # TODO: Add support for converting between pre-indexed and unindexed
+        if self._index is False:
+            return set([self.DRAW_PRE_INDEXED])
+        else:
+            return set([self.DRAW_UNINDEXED])
+
+    def set_data(self, pos=None, index=None):
+        if pos is not None:
+            self._pos = pos
+        if index is not None:
+            self._index = index
+        # TODO: might be better to re-upload data rather than creating
+        # a new VB, if possible.
+        self._vbo = None
+        self.update()
+
+    @property
+    def vbo(self):
+        if self._vbo is None:
+            self._vbo = gloo.VertexBuffer(self._pos)
+        return self._vbo
+
+    @property
+    def ibo(self):
+        if self._ibo is None:
+            self._ibo = gloo.IndexBuffer(self._index)
+        return self._ibo
+
+    def activate(self, program, draw_mode):
+        #self._funcs['local_position']['xyz_pos'] = ('attribute', 'vec3',
+                                                    #self.vbo)
+        self._funcs['local_position']['xyz_pos'] = self.vbo
+
+    @property
+    def index(self):
+        if self._index is False:
+            return None
+        else:
+            return self.ibo
+
+
+class HeightFieldComponent(VisualComponent):
+    """
+    Generate vertex coordinate from 2D array of z-positions.
+
+    x,y will be generated in the vertex shader using uniforms that specify the
+    range.
+    """
+    SHADERS = dict(
+        local_position="""
+            vec4 input_z_pos() {
+                int xind = int($index % $x_size);
+                float x = $x_min + (xind * $x_step);
+                int yind = int($index % $y_size);
+                float y = $y_min + (yind * $y_step);
+                return vec4(x, y, $z_pos, 1.0);
+            }
+        """)
+
+    def __init__(self, z=None):
+        super(HeightFieldComponent, self).__init__()
+        self._z = None
+        self._vbo = None
+        if z is not None:
+            self.set_data(z)
+
+    @property
+    def supported_draw_modes(self):
+        # TODO: add support for pre-indexed data
+        # (possibly here, possibly in another component class?)
+        return set([self.DRAW_UNINDEXED])
+
+    def set_data(self, z):
+        if self._z is None or self._z.shape != z.shape:
+            # if this data has a new shape, we need a new index buffer
+            self._ibo = None
+
+        self._z = z
+        # TODO: might be better to re-upload data rather than creating
+        # a new VB, if possible.
+        self._vbo = None
+
+        self.update()
+
+    @property
+    def vbo(self):
+        if self._vbo is None:
+            self._vbo = gloo.VertexBuffer(self._z)
+            self._index = gloo.VertexBuffer(np.arange(self._z.size))
+        return self._vbo
+
+    def activate(self, program, draw_mode):
+        self._funcs['local_position']['z_pos'] = self.vbo  # attribute vec3
+
+    @property
+    def index(self):
+        """
+        The IndexBuffer used by this input component.
+        """
+        if self._ibo is None:
+            cols = self._z.shape[1]-1
+            rows = self._z.shape[0]-1
+            faces = np.empty((cols*rows*2, 3), dtype=np.uint)
+            rowtemplate1 = (np.arange(cols).reshape(cols, 1) +
+                            np.array([[0, 1, cols+1]]))
+            rowtemplate2 = (np.arange(cols).reshape(cols, 1) +
+                            np.array([[cols+1, 1, cols+2]]))
+            for row in range(rows):
+                start = row * cols * 2
+                faces[start:start+cols] = rowtemplate1 + row * (cols+1)
+                faces[start+cols:start+(cols*2)] = (rowtemplate2 +
+                                                    row * (cols+1))
+            self._ibo = gloo.IndexBuffer(faces)
+        return self._ibo
diff --git a/vispy/scene/entity.py b/vispy/scene/entity.py
new file mode 100644
index 0000000..3f6bb41
--- /dev/null
+++ b/vispy/scene/entity.py
@@ -0,0 +1,331 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from . import transforms
+from ..util.event import EmitterGroup, Event
+from .events import SceneDrawEvent, SceneMouseEvent
+from .transforms import NullTransform, create_transform
+
+
+class Entity(object):
+    """ Base class to represent a citizen of a scene.
+
+    Typically an Entity is used to visualize something, although this is not
+    strictly necessary. It may for instance also be used as a container to
+    apply a certain transformation to a group of objects, or an object that
+    performs a specific task without being visible.
+
+    Each entity can have zero or more children. Each entity will
+    typically have one parent, although multiple parents are allowed.
+    It is recommended to use multi-parenting with care.
+
+    Parameters
+    ----------
+    parent : Entity
+        The parent of the Entity.
+    name : str
+        The name used to identify the entity.
+    """
+
+    def __init__(self, parent=None, name=None):
+        self.events = EmitterGroup(source=self,
+                                   auto_connect=True,
+                                   parents_change=Event,
+                                   active_parent_change=Event,
+                                   children_change=Event,
+                                   mouse_press=SceneMouseEvent,
+                                   mouse_move=SceneMouseEvent,
+                                   mouse_release=SceneMouseEvent,
+                                   mouse_wheel=SceneMouseEvent,
+                                   draw=SceneDrawEvent,
+                                   children_drawn=SceneDrawEvent,
+                                   update=Event,
+                                   transform_change=Event,
+                                   )
+        self.name = name
+
+        # Entities are organized in a parent-children hierarchy
+        # todo: I think we want this to be a list. The order *may* be important
+        # for some drawing systems. Using a set may lead to inconsistency
+        self._children = set()
+        # TODO: use weakrefs for parents.
+        self._parents = set()
+        if parent is not None:
+            self.parents = parent
+            
+        self._document = None
+
+        # Components that all entities in vispy have
+        # todo: default transform should be trans-scale-rot transform
+        self._transform = transforms.NullTransform()
+    
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, n):
+        self._name = n
+
+    @property
+    def children(self):
+        """ The list of children of this entity. The children are in
+        arbitrary order.
+        """
+        return list(self._children)
+
+    @property
+    def parent(self):
+        """ Get/set the parent. If the entity has multiple parents while
+        using this property as a getter, an error is raised.
+        """
+        if not self._parents:
+            return None
+        elif len(self._parents) == 1:
+            return tuple(self._parents)[0]
+        else:
+            raise RuntimeError('Ambiguous parent: there are multiple parents.')
+
+    @parent.setter
+    def parent(self, parent):
+        # This is basically an alias
+        self.parents = parent
+
+    @property
+    def parents(self):
+        """ Get/set a tuple of parents.
+        """
+        return tuple(self._parents)
+
+    @parents.setter
+    def parents(self, parents):
+        # Test input
+        if isinstance(parents, Entity):
+            parents = (parents,)
+        if not hasattr(parents, '__iter__'):
+            raise ValueError("Entity.parents must be iterable (got %s)"
+                             % type(parents))
+
+        # Test that all parents are entities
+        for p in parents:
+            if not isinstance(p, Entity):
+                raise ValueError('A parent of an entity must be an entity too,'
+                                 ' not %s.' % p.__class__.__name__)
+
+        # convert to set
+        prev = self._parents.copy()
+        parents = set(parents)
+
+        with self.events.parents_change.blocker():
+            # Remove from parents
+            for parent in prev - parents:
+                self.remove_parent(parent)
+            # Add new
+            for parent in parents - prev:
+                self.add_parent(parent)
+
+        self.events.parents_change(new=parents, old=prev)
+
+    def add_parent(self, parent):
+        if parent in self._parents:
+            return
+        self._parents.add(parent)
+        parent._add_child(self)
+        self.events.parents_change(added=parent)
+        self.update()
+
+    def remove_parent(self, parent):
+        if parent not in self._parents:
+            raise ValueError("Parent not in set of parents for this entity.")
+        self._parents.remove(parent)
+        parent._remove_child(self)
+        self.events.parents_change(removed=parent)
+
+    def _add_child(self, ent):
+        self._children.add(ent)
+        self.events.children_change(added=ent)
+        ent.events.update.connect(self.events.update)
+
+    def _remove_child(self, ent):
+        self._children.remove(ent)
+        self.events.children_change(removed=ent)
+        ent.events.update.disconnect(self.events.update)
+
+    @property
+    def document(self):
+        """ The document is an optional property that is an entity representing
+        the coordinate system from which this entity should make physical 
+        measurements such as px, mm, pt, in, etc. This coordinate system 
+        should be used when determining line widths, font sizes, and any
+        other lengths specified in physical units.
+        
+        The default is None; in this case, a default document is used during
+        drawing (usually this is supplied by the SceneCanvas).
+        """
+        return self._document
+    
+    @document.setter
+    def document(self, doc):
+        if doc is not None and not isinstance(doc, Entity):
+            raise TypeError("Document property must be Entity or None.")
+        self._document = doc
+        self.update()
+
+    @property
+    def transform(self):
+        """ The transform that maps the local coordinate frame to the
+        coordinate frame of the parent.
+        """
+        return self._transform
+
+    @transform.setter
+    def transform(self, tr):
+        if self._transform is not None:
+            self._transform.changed.disconnect(self._transform_changed)
+        assert isinstance(tr, transforms.BaseTransform)
+        self._transform = tr
+        self._transform.changed.connect(self._transform_changed)
+        self._transform_changed(None)
+
+    def set_transform(self, type, *args, **kwds):
+        """ Create a new transform of *type* and assign it to this entity.
+        All extra arguments are used in the construction of the transform.
+        """
+        self.transform = create_transform(type, *args, **kwds)
+
+    def _transform_changed(self, event):
+        self.events.transform_change()
+        self.update()
+
+    def _parent_chain(self):
+        """
+        Return the chain of parents starting from this entity. The chain ends
+        at the first entity with either no parents or multiple parents.
+        """
+        chain = [self]
+        while True:
+            try:
+                parent = chain[-1].parent
+            except Exception:
+                break
+            if parent is None:
+                break
+            chain.append(parent)
+        return chain
+
+    def describe_tree(self, with_transform=False):
+        """Create tree diagram of children
+
+        Parameters
+        ----------
+        with_transform : bool
+            If true, add information about entity transform types.
+
+        Returns
+        ----------
+        tree : str
+            The tree diagram.
+        """
+        # inspired by https://github.com/mbr/asciitree/blob/master/asciitree.py
+        return self._describe_tree('', with_transform)
+
+    def _describe_tree(self, prefix, with_transform):
+        """Helper function to actuall construct the tree"""
+        extra = ': "%s"' % self.name if self.name is not None else ''
+        if with_transform:
+            extra += (' [%s]' % self.transform.__class__.__name__)
+        output = ''
+        if len(prefix) > 0:
+            output += prefix[:-3]
+            output += '  +--'
+        output += '%s%s\n' % (self.__class__.__name__, extra)
+
+        n_children = len(self.children)
+        for ii, child in enumerate(self.children):
+            sub_prefix = prefix + ('   ' if ii+1 == n_children else '  |')
+            output += child._describe_tree(sub_prefix, with_transform)
+        return output
+
+    def common_parent(self, entity):
+        """
+        Return the common parent of two entities. If the entities have no
+        common parent, return None. Does not search past multi-parent branches.
+        """
+        p1 = self._parent_chain()
+        p2 = entity._parent_chain()
+        for p in p1:
+            if p in p2:
+                return p
+        return None
+        
+    def entity_transform(self, entity):
+        """
+        Return the transform that maps from the coordinate system of
+        *entity* to the local coordinate system of *self*.
+        
+        Note that there must be a _single_ path in the scenegraph that connects
+        the two entities; otherwise an exception will be raised.        
+        """
+        cp = self.common_parent(entity)
+        # First map from entity to common parent
+        tr = NullTransform()
+        
+        while entity is not cp:
+            if entity.transform is not None:
+                tr = entity.transform * tr
+            
+            entity = entity.parent
+        
+        if entity is self:
+            return tr
+        
+        # Now map from common parent to self
+        tr2 = cp.entity_transform(self)
+        return tr2.inverse * tr
+        
+    def _process_mouse_event(self, event):
+        """
+        Propagate a mouse event through the scene tree starting at this Entity.
+        """
+        # 1. find all entities whose mouse-area includes the click point.
+        # 2. send the event to each entity one at a time
+        #    (we should use a specialized emitter for this, rather than
+        #     rebuild the emitter machinery!)
+
+        # TODO: for now we send the event to all entities; need to use
+        # picking to decide which entities should receive the event.
+        for enter, path in self.walk():
+            event._set_path(path)
+            entity = path[-1]
+            getattr(entity.events, event.type)(event)
+
+    def bounds(self, mode, axis):
+        """ Return the (min, max) bounding values describing the location of
+        this entity in its local coordinate system.
+        
+        Parameters
+        ----------
+        mode : str
+            Describes the type of boundary requested. Can be "visual", "data",
+            or "mouse".
+        axis : 0, 1, 2
+            The axis along which to measure the bounding values.
+        
+        Returns
+        -------
+        None or (min, max) tuple. 
+        """
+        return None
+
+    def update(self):
+        """
+        Emit an event to inform Canvases that this Entity needs to be redrawn.
+        """
+        self.events.update()
+
+    def __repr__(self):
+        name = "" if self.name is None else " name="+self.name
+        return "<%s%s at 0x%x>" % (self.__class__.__name__, name, id(self))
diff --git a/vispy/scene/events.py b/vispy/scene/events.py
new file mode 100644
index 0000000..0522bd6
--- /dev/null
+++ b/vispy/scene/events.py
@@ -0,0 +1,378 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from ..util.event import Event
+from .transforms import TransformCache
+
+
+class SceneEvent(Event):
+    """
+    SceneEvent is an Event that tracks its path through a scenegraph,
+    beginning at a Canvas. It exposes information useful during drawing
+    and user interaction.
+    """
+
+    def __init__(self, type, canvas, transform_cache=None):
+        super(SceneEvent, self).__init__(type=type)
+        self._canvas = canvas
+
+        # Init stacks
+        self._stack = []  # list of entities
+        self._stack_ids = set()
+        self._viewbox_stack = []
+        self._doc_stack = []
+        if transform_cache is None:
+            transform_cache = TransformCache()
+        self._transform_cache = transform_cache
+
+    @property
+    def canvas(self):
+        """ The Canvas that originated this SceneEvent
+        """
+        return self._canvas
+
+    @property
+    def viewbox(self):
+        """ The current viewbox.
+        """
+        if len(self._viewbox_stack) > 0:
+            return self._viewbox_stack[-1]
+        else:
+            return None
+
+    @property
+    def path(self):
+        """ The path of Entities leading from the root SubScene to the
+        current recipient of this Event.
+        """
+        return self._stack
+
+    def push_entity(self, entity):
+        """ Push an entity on the stack. """
+        self._stack.append(entity)
+        if id(entity) in self._stack_ids:
+            raise RuntimeError("Scenegraph cycle detected; cannot push %r" % 
+                               entity)
+        self._stack_ids.add(id(entity))
+        doc = entity.document
+        if doc is not None:
+            self.push_document(doc)
+
+    def pop_entity(self):
+        """ Pop an entity from the stack. """
+        ent = self._stack.pop(-1)
+        self._stack_ids.remove(id(ent))
+        if ent.document is not None:
+            assert ent.document == self.pop_document()
+        return ent
+
+    def push_viewbox(self, viewbox):
+        self._viewbox_stack.append(viewbox)
+
+    def pop_viewbox(self):
+        return self._viewbox_stack.pop(-1)
+
+    def push_document(self, doc):
+        self._doc_stack.append(doc)
+
+    def pop_document(self):
+        return self._doc_stack.pop(-1)
+
+    def push_viewport(self, viewport):
+        """ Push a viewport (x, y, w, h) on the stack. It is the
+        responsibility of the caller to ensure the given values are
+        int. The viewport's origin is defined relative to the current
+        viewport.
+        """
+        self.canvas.push_viewport(viewport)
+
+    def pop_viewport(self):
+        """ Pop a viewport from the stack.
+        """
+        return self.canvas.pop_viewport()
+
+    def push_fbo(self, viewport, fbo, transform):
+        """ Push an FBO on the stack, together with the new viewport.
+        and the transform to the FBO.
+        """
+        self.canvas.push_fbo(viewport, fbo, transform)
+
+    def pop_fbo(self):
+        """ Pop an FBO from the stack.
+        """
+        return self.canvas.pop_fbo()
+
+    #
+    # Begin transforms
+    #
+
+    @property
+    def document_cs(self):
+        """ The entity for the current document coordinate system. The
+        coordinate system of this Entity is used for making physical
+        measurements--px, mm, in, etc.
+        """
+        return self._doc_stack[-1]
+
+    @property
+    def canvas_cs(self):
+        """ The entity for the current canvas coordinate system. This cs 
+        represents the logical pixels of the canvas being drawn, with the 
+        origin in upper-left, and the canvas (width, height) in the bottom 
+        right. This coordinate system is most often used for handling mouse
+        input.
+        """
+        return self.canvas.canvas_cs
+
+    @property
+    def framebuffer_cs(self):
+        """ The entity for the current framebuffer coordinate system. This
+        coordinate system corresponds to the physical pixels being rendered
+        to, with the origin in lower-right, and the framebufer (width, height)
+        in upper-left. It is used mainly for making antialiasing measurements.
+        """
+        return self.canvas.framebuffer_cs
+
+    @property
+    def render_cs(self):
+        """ Return entity for the normalized device coordinate system. This
+        coordinate system is the obligatory output of GLSL vertex shaders, 
+        with (-1, -1) in bottom-left, and (1, 1) in top-right. This coordinate
+        system is frequently used for rendering visuals because all vertices
+        must ultimately be mapped here.
+        """
+        return self.canvas.render_cs
+
+    def document_transform(self, entity=None):
+        """ Return the transform that maps from *entity* to the current
+        document coordinate system.
+
+        If *entity* is not specified, then the top entity on the stack is used.
+        """
+        return self.entity_transform(map_to=self.document_cs, map_from=entity)
+
+    def map_entity_to_document(self, entity, obj):
+        return self.document_transform(entity).map(obj)
+
+    def map_document_to_entity(self, entity, obj):
+        return self.document_transform(entity).imap(obj)
+
+    def map_to_document(self, obj):
+        return self.document_transform().map(obj)
+
+    def map_from_document(self, obj):
+        return self.document_transform().imap(obj)
+
+    def canvas_transform(self, entity=None):
+        """ Return the transform that maps from *entity* to the current
+        logical-pixel coordinate system defined by the Canvas.
+
+        Canvas_transform is used mainly for mouse interaction.
+        For measuring distance in physical units, the use of document_transform
+        is preferred.
+
+        If *entity* is not specified, then the top entity on the stack is used.
+        """
+        return self.entity_transform(map_to=self.canvas_cs, map_from=entity)
+
+    def map_entity_to_canvas(self, entity, obj):
+        return self.canvas_transform(entity).map(obj)
+
+    def map_canvas_to_entity(self, entity, obj):
+        return self.canvas_transform(entity).imap(obj)
+
+    def map_to_canvas(self, obj):
+        return self.canvas_transform().map(obj)
+
+    def map_from_canvas(self, obj):
+        return self.canvas_transform().imap(obj)
+
+    def framebuffer_transform(self, entity=None):
+        """ Return the transform that maps from *entity* to the current
+        framebuffer coordinate system.
+
+        If *entity* is not specified, then the top entity on the stack is used.
+        """
+        return self.entity_transform(map_to=self.framebuffer_cs, 
+                                     map_from=entity)
+
+    def map_entity_to_framebuffer(self, entity, obj):
+        return self.framebuffer_transform(entity).map(obj)
+
+    def map_framebuffer_to_entity(self, entity, obj):
+        return self.framebuffer_transform(entity).imap(obj)
+
+    def map_to_framebuffer(self, obj):
+        return self.framebuffer_transform().map(obj)
+
+    def map_from_framebuffer(self, obj):
+        return self.framebuffer_transform().imap(obj)
+
+    @property
+    def render_transform(self):
+        """ The transform that maps from the current entity to
+        normalized device coordinates within the current glViewport and
+        FBO.
+
+        This transform consists of the full_transform prepended by a
+        correction for the current glViewport and/or FBO.
+
+        Most entities will use this transform when drawing.
+        """
+        return self._transform_cache.get([e.transform for e in self._stack])
+
+    @property
+    def scene_transform(self):
+        """ The transform that maps from the current entity to the first
+        scene in its ancestry.
+        """
+        view = self._viewbox_stack[-1]
+        return self.entity_transform(map_to=view.scene)
+
+    @property
+    def view_transform(self):
+        """ The transform that maps from the current entity to the first
+        viewbox in its ancestry.
+        """
+        view = self._viewbox_stack[-1]
+        return self.entity_transform(map_to=view)
+
+    def entity_transform(self, map_to=None, map_from=None):
+        """ Return the transform from *map_from* to *map_to*, using the
+        current entity stack to resolve parent ambiguities if needed.
+
+        By default, *map_to* is the normalized device coordinate system,
+        and *map_from* is the current top entity on the stack.
+        """
+        if map_to is None:
+            map_to = self.render_cs
+        if map_from is None:
+            map_from = self._stack[-1]
+
+        fwd_path = self._entity_path(map_from, map_to)
+        fwd_path.reverse()
+
+        if fwd_path[0] is map_to:
+            rev_path = []
+            fwd_path = fwd_path[1:]
+        else:
+            # If we have still not reached the end, try traversing from the
+            # opposite end and stop when paths intersect
+            rev_path = self._entity_path(map_to, self._stack[0])
+            connected = False
+            for i in range(1, len(rev_path)):
+                if rev_path[i] in fwd_path:
+                    rev_path = rev_path[:i]
+                    connected = True
+
+            if not connected:
+                raise RuntimeError("Unable to find unique path from %r to %r" %
+                                   (map_from, map_to))
+
+        transforms = ([e.transform for e in fwd_path] +
+                      [e.transform.inverse for e in rev_path])
+        return self._transform_cache.get(transforms)
+
+    def _entity_path(self, start, end):
+        """
+        Return the path of parents leading from *start* to *end*, using the
+        entity stack to resolve multi-parent branches.
+
+        If *end* is never reached, then the path is assembled as far as
+        possible and returned.
+        """
+        path = [start]
+
+        # first, get parents directly from entity
+        entity = start
+        while id(entity) not in self._stack_ids:
+            if entity is end or len(entity.parents) != 1:
+                return path
+            entity = entity.parent
+            path.append(entity)
+
+        # if we have not reached the end, follow _stack if possible.
+        if path[-1] is not end:
+            try:
+                ind = self._stack.index(entity)
+                # copy stack onto path one entity at a time
+                while ind > -1 and path[-1] is not end:
+                    ind -= 1
+                    path.append(self._stack[ind])
+            except IndexError:
+                pass
+
+        return path
+
+
+class SceneDrawEvent(SceneEvent):
+    def __init__(self, event, canvas, **kwds):
+        self.draw_event = event
+        super(SceneDrawEvent, self).__init__(type='draw', canvas=canvas,
+                                             **kwds)
+
+
+class SceneMouseEvent(SceneEvent):
+    """ Represents a mouse event that occurred on a SceneCanvas. This event is
+    delivered to all entities whose mouse interaction area is under the event. 
+    """
+    def __init__(self, event, canvas, **kwds):
+        self.mouse_event = event
+        super(SceneMouseEvent, self).__init__(type=event.type, canvas=canvas,
+                                              **kwds)
+
+    @property
+    def pos(self):
+        """ The position of this event in the local coordinate system of the 
+        visual.
+        """
+        return self.map_from_canvas(self.mouse_event.pos)
+
+    @property
+    def last_event(self):
+        """ The mouse event immediately prior to this one. This
+        property is None when no mouse buttons are pressed.
+        """
+        if self.mouse_event.last_event is None:
+            return None
+        ev = self.copy()
+        ev.mouse_event = self.mouse_event.last_event
+        return ev
+
+    @property
+    def press_event(self):
+        """ The mouse press event that initiated a mouse drag, if any. 
+        """
+        if self.mouse_event.press_event is None:
+            return None
+        ev = self.copy()
+        ev.mouse_event = self.mouse_event.press_event
+        return ev
+
+    @property
+    def button(self):
+        """ The button pressed or released on this event.
+        """
+        return self.mouse_event.button
+
+    @property
+    def buttons(self):
+        """ A list of all buttons currently pressed on the mouse.
+        """
+        return self.mouse_event.buttons
+
+    @property
+    def delta(self):
+        """ The increment by which the mouse wheel has moved.
+        """
+        return self.mouse_event.delta
+
+    def copy(self):
+        ev = self.__class__(self.mouse_event, self._canvas)
+        ev._stack = self._stack[:]
+        #ev._ra_stack = self._ra_stack[:]
+        ev._viewbox_stack = self._viewbox_stack[:]
+        return ev
diff --git a/vispy/scene/shaders/__init__.py b/vispy/scene/shaders/__init__.py
new file mode 100644
index 0000000..6ffb2df
--- /dev/null
+++ b/vispy/scene/shaders/__init__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Provides functionality for composing shaders from multiple GLSL
+code snippets.
+"""
+
+__all__ = ['ModularProgram', 'Function', 'MainFunction', 'Variable', 'Varying',
+           'FunctionChain', 'Compiler']
+
+from .program import ModularProgram  # noqa
+from .function import (Function, MainFunction, Variable, Varying,  # noqa
+                       FunctionChain)  # noqa
+from .compiler import Compiler  # noqa
diff --git a/vispy/scene/shaders/compiler.py b/vispy/scene/shaders/compiler.py
new file mode 100644
index 0000000..1fa1dd3
--- /dev/null
+++ b/vispy/scene/shaders/compiler.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import re
+
+from ... import gloo
+
+
+class Compiler(object):
+    """
+    Compiler is used to convert Function and Variable instances into 
+    ready-to-use GLSL code. This class handles name mangling to ensure that
+    there are no name collisions amongst global objects. The final name of
+    each object may be retrieved using ``Compiler.__getitem__(obj)``.
+    
+    Accepts multiple root Functions as keyword arguments. ``compile()`` then
+    returns a dict of GLSL strings with the same keys.
+    
+    Example::
+    
+        # initialize with two main functions
+        compiler = Compiler(vert=v_func, frag=f_func)
+        
+        # compile and extract shaders
+        code = compiler.compile()
+        v_code = code['vert']
+        f_code = code['frag']
+        
+        # look up name of some object
+        name = compiler[obj]
+    
+    """
+    def __init__(self, **shaders):
+        # cache of compilation results for each function and variable
+        self._object_names = {}  # {object: name}
+        self.shaders = shaders
+
+    def __getitem__(self, item):
+        """
+        Return the name of the specified object, if it has been assigned one.        
+        """
+        return self._object_names[item]
+
+    def compile(self, pretty=True):
+        """ Compile all code and return a dict {name: code} where the keys 
+        are determined by the keyword arguments passed to __init__().
+        
+        Parameters
+        ----------
+        pretty : bool
+            If True, use a slower method to mangle object names. This produces
+            GLSL that is more readable.
+            If False, then the output is mostly unreadable GLSL, but is about 
+            10x faster to compile.
+        
+        """
+        # Authoritative mapping of {obj: name}
+        self._object_names = {}
+        
+        #
+        # 1. collect list of dependencies for each shader
+        #
+        
+        # maps {shader_name: [deps]}
+        self._shader_deps = {}
+        
+        for shader_name, shader in self.shaders.items():
+            this_shader_deps = []
+            self._shader_deps[shader_name] = this_shader_deps
+            dep_set = set()
+            
+            for dep in shader.dependencies(sort=True):
+                # visit each object no more than once per shader
+                if dep.name is None or dep in dep_set:
+                    continue
+                this_shader_deps.append(dep)
+                dep_set.add(dep)
+
+        #
+        # 2. Assign names to all objects.
+        #
+        if pretty:
+            self._rename_objects_pretty()
+        else:
+            self._rename_objects_fast()
+        
+        #
+        # 3. Now we have a complete namespace; concatenate all definitions
+        # together in topological order.
+        #
+        compiled = {}
+        obj_names = self._object_names
+        
+        for shader_name, shader in self.shaders.items():
+            code = ['// Generated code by function composition', 
+                    '#version 120', '']
+            for dep in self._shader_deps[shader_name]:
+                dep_code = dep.definition(obj_names)
+                if dep_code is not None:
+                    # strip out version pragma if present; 
+                    regex = r'#version (\d+)'
+                    m = re.search(regex, dep_code)
+                    if m is not None:
+                        # check requested version
+                        if m.group(1) != '120':
+                            raise RuntimeError("Currently only GLSL #version "
+                                               "120 is supported.")
+                        dep_code = re.sub(regex, '', dep_code)
+                    code.append(dep_code)
+                
+            compiled[shader_name] = '\n'.join(code)
+            
+        self.code = compiled
+        return compiled
+
+    def _rename_objects_fast(self):
+        """ Rename all objects quickly to guaranteed-unique names using the 
+        id() of each object.
+        
+        This produces mostly unreadable GLSL, but is about 10x faster to 
+        compile.
+        """
+        for shader_name, deps in self._shader_deps.items():
+            for dep in deps:
+                name = dep.name
+                if name != 'main':
+                    ext = '_%x' % id(dep)
+                    name = name[:32-len(ext)] + ext
+                self._object_names[dep] = name
+            
+    def _rename_objects_pretty(self):
+        """ Rename all objects like "name_1" to avoid conflicts. Objects are
+        only renamed if necessary. 
+        
+        This method produces more readable GLSL, but is rather slow.
+        """
+        #
+        # 1. For each object, add its static names to the global namespace
+        #    and make a list of the shaders used by the object.
+        #
+        
+        # {name: obj} mapping for finding unique names
+        # initialize with reserved keywords.
+        self._global_ns = dict([(kwd, None) for kwd in gloo.util.KEYWORDS])
+        # functions are local per-shader
+        self._shader_ns = dict([(shader, {}) for shader in self.shaders])
+
+        # for each object, keep a list of shaders the object appears in
+        obj_shaders = {}
+        
+        for shader_name, deps in self._shader_deps.items():
+            for dep in deps:
+                # Add static names to namespace
+                for name in dep.static_names():
+                    self._global_ns[name] = None
+                    
+                obj_shaders.setdefault(dep, []).append(shader_name)
+                
+        #
+        # 2. Assign new object names
+        #
+        name_index = {}
+        for obj, shaders in obj_shaders.items():
+            name = obj.name
+            if self._name_available(obj, name, shaders):
+                # hooray, we get to keep this name
+                self._assign_name(obj, name, shaders)
+            else:
+                # boo, find a new name
+                while True:
+                    index = name_index.get(name, 0) + 1
+                    name_index[name] = index
+                    ext = '_%d' % index
+                    new_name = name[:32-len(ext)] + ext
+                    if self._name_available(obj, new_name, shaders):
+                        self._assign_name(obj, new_name, shaders)
+                        break
+        
+    def _is_global(self, obj):
+        """ Return True if *obj* should be declared in the global namespace. 
+        
+        Some objects need to be declared only in per-shader namespaces:
+        functions, static variables, and const variables may all be given
+        different definitions in each shader.
+        """
+        # todo: right now we assume all Variables are global, and all 
+        # Functions are local. Is this actually correct? Are there any
+        # global functions? Are there any local variables?
+        from .function import Variable
+        return isinstance(obj, Variable)
+
+    def _name_available(self, obj, name, shaders):
+        """ Return True if *name* is available for *obj* in *shaders*.
+        """
+        if name in self._global_ns:
+            return False
+        shaders = self.shaders if self._is_global(obj) else shaders
+        for shader in shaders:
+            if name in self._shader_ns[shader]:
+                return False
+        return True
+
+    def _assign_name(self, obj, name, shaders):
+        """ Assign *name* to *obj* in *shaders*.
+        """
+        if self._is_global(obj):
+            assert name not in self._global_ns
+            self._global_ns[name] = obj
+        else:
+            for shader in shaders:
+                ns = self._shader_ns[shader]
+                assert name not in ns
+                ns[name] = obj
+        self._object_names[obj] = name
diff --git a/vispy/scene/shaders/function.py b/vispy/scene/shaders/function.py
new file mode 100644
index 0000000..23ee136
--- /dev/null
+++ b/vispy/scene/shaders/function.py
@@ -0,0 +1,1134 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Classses representing GLSL objects (functions, variables, etc) that may be
+composed together to create complete shaders. 
+See the docstring of Function for details.
+
+Details
+-------
+
+A complete GLSL program is composed of ShaderObjects, each of which may be used
+inline as an expression, and some of which include a definition that must be
+included on the final code. ShaderObjects keep track of a hierarchy of
+dependencies so that all necessary code is included at compile time, and
+changes made to any object may be propagated to the root of the hierarchy to 
+trigger a recompile.
+"""
+
+import re
+import numpy as np
+
+from ...util.event import EventEmitter, Event
+from ...util.eq import eq
+from ...util import logger
+from ...ext.ordereddict import OrderedDict
+from ...ext.six import string_types
+from . import parsing
+from .compiler import Compiler
+
+VARIABLE_TYPES = ('const', 'uniform', 'attribute', 'varying', 'inout')
+
+
+class ShaderChangeEvent(Event):
+    def __init__(self, code_changed=False, value_changed=False, **kwds):
+        Event.__init__(self, type='shader_change', **kwds)
+        self.code_changed = code_changed
+        self.value_changed = value_changed
+
+
+class ShaderObject(object):
+    """ Base class for all objects that may be included in a GLSL program
+    (Functions, Variables, Expressions).
+    
+    Shader objects have a *definition* that defines the object in GLSL, an 
+    *expression* that is used to reference the object, and a set of 
+    *dependencies* that must be declared before the object is used.
+    
+    Dependencies are tracked hierarchically such that changes to any object
+    will be propagated up the dependency hierarchy to trigger a recompile.
+    """
+    
+    @classmethod
+    def create(self, obj, ref=None):
+        """ Convert *obj* to a new ShaderObject. If the output is a Variable
+        with no name, then set its name using *ref*. 
+        """
+        if isinstance(ref, Variable):
+            ref = ref.name
+        elif isinstance(ref, string_types) and ref.startswith('gl_'):
+            # gl_ names not allowed for variables
+            ref = ref[3:].lower()
+        
+        if isinstance(obj, ShaderObject):
+            if isinstance(obj, Variable) and obj.name is None:
+                obj.name = ref
+        elif isinstance(obj, string_types):
+            obj = TextExpression(obj)
+        else:
+            obj = Variable(ref, obj)
+            # Try prepending the name to indicate attribute, uniform, varying
+            if obj.vtype and obj.vtype[0] in 'auv':
+                obj.name = obj.vtype[0] + '_' + obj.name 
+        
+        return obj
+    
+    def __init__(self):
+        # emitted when any part of the code for this object has changed,
+        # including dependencies.
+        self.changed = EventEmitter(source=self, event_class=ShaderChangeEvent)
+        
+        # objects that must be declared before this object's definition.
+        # {obj: refcount}
+        self._deps = OrderedDict()  # OrderedDict for consistent code output
+    
+    @property
+    def name(self):
+        """ The name of this shader object.
+        """
+        return None
+        
+    def definition(self, obj_names):
+        """ Return the GLSL definition for this object. Use *obj_names* to
+        determine the names of dependencies.
+        """
+        return None
+    
+    def expression(self, obj_names):
+        """ Return the GLSL expression used to reference this object inline.
+        """
+        return obj_names[self]
+    
+    def dependencies(self, sort=False):
+        """ Return all dependencies required to use this object. The last item 
+        in the list is *self*.
+        """
+        alldeps = []
+        if sort:
+            def key(obj):
+                # sort deps such that we get functions, variables, self.
+                if not isinstance(obj, Variable):
+                    return (0, 0)
+                else:
+                    return (1, obj.vtype)
+            
+            deps = sorted(self._deps, key=key)
+        else:
+            deps = self._deps
+        
+        for dep in deps:
+            alldeps.extend(dep.dependencies(sort=sort))
+        alldeps.append(self)
+        return alldeps
+
+    def static_names(self):
+        """ Return a list of names that are declared in this object's 
+        definition (not including the name of the object itself).
+        
+        These names will be reserved by the compiler when automatically 
+        determining object names.
+        """
+        return []
+    
+    def _add_dep(self, dep):
+        """ Increment the reference count for *dep*. If this is a new 
+        dependency, then connect to its *changed* event.
+        """
+        if dep in self._deps:
+            self._deps[dep] += 1
+        else:
+            self._deps[dep] = 1
+            dep.changed.connect(self._dep_changed)
+
+    def _remove_dep(self, dep):
+        """ Decrement the reference count for *dep*. If the reference count 
+        reaches 0, then the dependency is removed and its *changed* event is
+        disconnected.
+        """
+        refcount = self._deps[dep]
+        if refcount == 1:
+            self._deps.pop(dep)
+            dep.changed.disconnect(self._dep_changed)
+        else:
+            self._deps[dep] -= 1
+        
+    def _dep_changed(self, event):
+        """ Called when a dependency's expression has changed.
+        """
+        logger.debug("ShaderObject changed: %r" % event.source)
+        self.changed(event)
+    
+    def compile(self):
+        """ Return a compilation of this object and its dependencies. 
+        
+        Note: this is mainly for debugging purposes; the names in this code
+        are not guaranteed to match names in any other compilations. Use
+        Compiler directly to ensure consistent naming across multiple objects. 
+        """
+        compiler = Compiler(obj=self)
+        return compiler.compile()['obj']
+    
+    def __repr__(self):
+        if self.name is not None:
+            return '<%s "%s" at 0x%x>' % (self.__class__.__name__, 
+                                          self.name, id(self))
+        else:
+            return '<%s at 0x%x>' % (self.__class__.__name__, id(self))
+
+
+class Function(ShaderObject):
+    """Representation of a GLSL function
+    
+    Objects of this class can be used for re-using and composing GLSL
+    snippets. Each Function consists of a GLSL snippet in the form of
+    a function. The code may have template variables that start with
+    the dollar sign. These stubs can be replaced with expressions using
+    the index operation. Expressions can be:
+    
+    * plain text that is inserted verbatim in the code
+    * a Function object or a call to a funcion
+    * a Variable (or Varying) object
+    * float, int, tuple are automatically turned into a uniform Variable
+    * a VertexBuffer is automatically turned into an attribute Variable
+    
+    Examples
+    --------
+    This example shows the basic usage of the Function class::
+
+        vert_code_template = Function('''
+            void main() {
+            gl_Position = $pos;
+            gl_Position.x += $xoffset;
+            gl_Position.y += $yoffset;
+        }''')
+        
+        scale_transform = Function('''
+        vec4 transform_scale(vec4 pos){
+            return pos * $scale;
+        }''')
+        
+        # If you get the function from a snippet collection, always
+        # create new Function objects to ensure they are 'fresh'.
+        vert_code = Function(vert_code_template)
+        trans1 = Function(scale_transform)
+        trans2 = Function(scale_transform)  # trans2 != trans1
+        
+        # Three ways to assign to template variables:
+        #
+        # 1) Assign verbatim code
+        vert_code['xoffset'] = '(3.0 / 3.1415)'
+        
+        # 2) Assign a value (this creates a new uniform or attribute)
+        vert_code['yoffset'] = 5.0
+        
+        # 3) Assign a function call expression
+        pos_var = Variable('attribute vec4 a_position')
+        vert_code['pos'] = trans1(trans2(pos_var))
+        
+        # Transforms also need their variables set
+        trans1['scale'] = 0.5
+        trans2['scale'] = (1.0, 0.5, 1.0, 1.0)
+        
+        # You can actually change any code you want, but use this with care!
+        vert_code.replace('gl_Position.y', 'gl_Position.z')
+        
+        # Finally, you can set special variables explicitly. This generates
+        # a new statement at the end of the vert_code function.
+        vert_code['gl_PointSize'] = '10.'
+    
+    
+    If we use ``vert_code.compile()`` we get::
+
+        attribute vec4 a_position;
+        uniform float u_yoffset;
+        uniform float u_scale_1;
+        uniform vec4 u_scale_2;
+        uniform float u_pointsize;
+        
+        vec4 transform_scale_1(vec4 pos){
+            return pos * u_scale_1;
+        }
+        
+        vec4 transform_scale_2(vec4 pos){
+            return pos * u_scale_2;
+        }
+        
+        void main() {
+            gl_Position = transform_scale_1(transform_scale_2(a_position));
+            gl_Position.x += (3.0 / 3.1415);
+            gl_Position.z += u_yoffset;
+        
+            gl_PointSize = u_pointsize;
+        }
+    
+    Note how the two scale function resulted in two different functions
+    and two uniforms for the scale factors.
+    
+    Function calls
+    --------------
+    
+    As can be seen above, the arguments with which a function is to be
+    called must be specified by calling the Function object. The
+    arguments can be any of the expressions mentioned earlier. If the
+    signature is already specified in the template code, that function
+    itself must be given.
+    
+        code = Function('''
+            void main() {
+                vec4 position = $pos;
+                gl_Position = $scale(position)
+            }
+        ''')
+        
+        # Example of a function call with all possible three expressions
+        vert_code['pos'] = func1('3.0', 'uniform float u_param', func2())
+        
+        # For scale, the sigfnature is already specified
+        code['scale'] = scale_func  # Must not specify args
+    
+    Data for uniform and attribute variables
+    ----------------------------------------
+    To each variable a value can be associated. In fact, in most cases
+    the Function class is smart enough to be able to create a Variable
+    object if only the data is given.
+    
+        code['offset'] = Variable('uniform float offset')  # No data
+        code['offset'] = Variable('uniform float offset', 3.0)  # With data
+        code['offset'] = 3.0  # -> Uniform Variable
+        position['position'] = VertexBuffer()  # -> attribute Variable
+        
+        # Updating variables
+        code['offset'].value = 4.0
+        position['position'].value.set_data(...)
+    """
+    
+    def __init__(self, code, dependencies=None):
+        super(Function, self).__init__()
+        
+        # Add depencencies is given. This is to allow people to
+        # manually define deps for a function that they use.
+        if dependencies is not None:
+            for dep in dependencies:
+                self._add_dep(dep)
+        
+        # Get and strip code
+        if isinstance(code, Function):
+            code = code._code
+        elif not isinstance(code, string_types):
+            raise ValueError('Function needs a string or Function; got %s.' %
+                             type(code))
+        self._code = self._clean_code(code)
+        
+        # (name, args, rval)
+        self._signature = None
+        
+        # $placeholders parsed from the code
+        self._template_vars = None
+        
+        # Expressions replace template variables (also our dependencies)
+        self._expressions = OrderedDict()
+        
+        # Verbatim string replacements
+        self._replacements = OrderedDict()
+        
+        # Stuff to do at the end
+        self._post_hooks = OrderedDict()
+        
+        # Create static Variable instances for any global variables declared
+        # in the code
+        self._static_vars = None
+    
+    def __setitem__(self, key, val):
+        """ Setting of replacements through a dict-like syntax.
+        
+        Each replacement can be:
+        * verbatim code: ``fun1['foo'] = '3.14159'``
+        * a FunctionCall: ``fun1['foo'] = fun2()``
+        * a Variable: ``fun1['foo'] = Variable(...)`` (can be auto-generated)
+        """
+        # Check the key. Must be Varying, 'gl_X' or a known template variable
+        if isinstance(key, Variable): 
+            if key.vtype == 'varying':
+                if self.name != 'main':
+                    raise Exception("Varying assignment only alowed in 'main' "
+                                    "function.")
+                storage = self._post_hooks
+            else:
+                raise TypeError("Variable assignment only allowed for "
+                                "varyings, not %s (in %s)"
+                                % (key.vtype, self.name))
+        elif isinstance(key, string_types):
+            if any(map(key.startswith, 
+                       ('gl_PointSize', 'gl_Position', 'gl_FragColor'))):
+                storage = self._post_hooks
+            elif key in self.template_vars:
+                storage = self._expressions
+            else:
+                raise KeyError('Invalid template variable %r' % key)
+        else:
+            raise TypeError('In `function[key]` key must be a string or '
+                            'varying.')
+        
+        # If values already match, bail out now
+        if eq(storage.get(key), val):
+            return
+
+        # If we are only changing the value (and not the dtype) of a uniform,
+        # we can set that value and return immediately to avoid triggering a
+        # recompile.
+        if val is not None and not isinstance(val, Variable):
+            # We are setting a value. If there is already a variable set here,
+            # try just updating its value.
+            variable = storage.get(key, None)
+            if isinstance(variable, Variable):
+                try:
+                    variable.value = val
+                    return
+                except Exception:
+                    # Setting value on existing Variable failed for some
+                    # reason; will need to create a new Variable instead. 
+                    pass
+        
+        #print("SET: %s[%s] = %s => %s" % 
+        #     (self, key, storage.get(key, None), val))
+        
+        # Remove old references, if any
+        oldval = storage.pop(key, None)
+        if oldval is not None:
+            for obj in (key, oldval):
+                if isinstance(obj, ShaderObject):
+                    self._remove_dep(obj)
+
+        # Add new references
+        if val is not None:
+            val = ShaderObject.create(val, ref=key)
+            if isinstance(key, Varying):
+                # tell this varying to inherit properties from 
+                # its source attribute / expression.
+                key.link(val)
+            
+            # Store value and dependencies
+            storage[key] = val
+            for obj in (key, val):
+                if isinstance(obj, ShaderObject):
+                    self._add_dep(obj)
+        
+        # In case of verbatim text, we might have added new template vars
+        if isinstance(val, TextExpression):
+            for var in parsing.find_template_variables(val.expression()):
+                if var not in self.template_vars:
+                    self.template_vars.add(var.lstrip('$'))
+        
+        self.changed(code_changed=True, value_changed=True)
+    
+    def __getitem__(self, key):
+        """ Return a reference to a program variable from this function.
+
+        This allows variables between functions to be linked together::
+
+            func1['var_name'] = func2['other_var_name']
+
+        In the example above, the two local variables would be assigned to the
+        same program variable whenever func1 and func2 are attached to the same
+        program.
+        """
+        
+        try:
+            return self._expressions[key]
+        except KeyError:
+            pass
+        
+        try:
+            return self._post_hooks[key]
+        except KeyError:
+            pass
+        
+        if key not in self.template_vars:
+            raise KeyError('Invalid template variable %r' % key) 
+        else:
+            raise KeyError('No value known for key %r' % key)
+    
+    def __call__(self, *args):
+        """ Set the signature for this function and return an FunctionCall
+        object. Each argument can be verbatim code or a FunctionCall object.
+        """
+        return FunctionCall(self, args)
+    
+    ## Public API methods
+
+    @property
+    def signature(self):
+        if self._signature is None:
+            try:
+                self._signature = parsing.parse_function_signature(self._code)
+            except Exception as err:
+                raise ValueError('Invalid code: ' + str(err))
+        return self._signature
+    
+    @property
+    def name(self):
+        """ The function name. The name may be mangled in the final code
+        to avoid name clashes.
+        """
+        return self.signature[0]
+
+    @property
+    def args(self):
+        """
+        List of input arguments in the function signature::
+
+            [(arg_name, arg_type), ...]
+        """
+        return self.signature[1]
+
+    @property
+    def rtype(self):
+        """
+        The return type of this function.
+        """
+        return self.signature[2]
+    
+    @property
+    def code(self):
+        """ The template code used to generate the definition for this 
+        function.
+        """
+        return self._code
+    
+    @property
+    def template_vars(self):
+        if self._template_vars is None:
+            self._template_vars = self._parse_template_vars()
+        return self._template_vars
+
+    def static_names(self):
+        if self._static_vars is None:
+            self._static_vars = parsing.find_program_variables(self._code)
+        return list(self._static_vars.keys()) + [arg[0] for arg in self.args]
+
+    def replace(self, str1, str2):
+        """ Set verbatim code replacement
+        
+        It is strongly recommended to use function['$foo'] = 'bar' where
+        possible because template variables are less likely to changed
+        than the code itself in future versions of vispy.
+        
+        Parameters
+        ----------
+        str1 : str
+            String to replace
+        str2 : str
+            String to replace str1 with
+        """
+        if str2 != self._replacements.get(str1, None):
+            self._replacements[str1] = str2
+            self.changed(code_changed=True)
+            #self._last_changed = time.time()
+    
+    ## Private methods
+    
+    def _parse_template_vars(self):
+        """ find all template variables in self._code, excluding the
+        function name. 
+        """
+        template_vars = set()
+        for var in parsing.find_template_variables(self._code):
+            var = var.lstrip('$')
+            if var == self.name:
+                continue
+            template_vars.add(var)
+        return template_vars
+    
+    def _get_replaced_code(self, names):
+        """ Return code, with new name, expressions, and replacements applied.
+        """
+        code = self._code
+        
+        # Modify name
+        code = code.replace(" " + self.name + "(", " " + names[self] + "(")
+
+        # Apply string replacements first -- these may contain $placeholders
+        for key, val in self._replacements.items():
+            code = code.replace(key, val)
+        
+        # Apply post-hooks
+        
+        # Collect post lines
+        post_lines = []
+        for key, val in self._post_hooks.items():
+            if isinstance(key, Variable):
+                key = names[key]
+            if isinstance(val, ShaderObject):
+                val = val.expression(names)
+            line = '    %s = %s;' % (key, val)
+            post_lines.append(line)
+            
+        # Apply placeholders for hooks
+        post_text = '\n'.join(post_lines)
+        if post_text:
+            post_text = '\n' + post_text + '\n'
+        code = code.rpartition('}')
+        code = code[0] + post_text + code[1] + code[2]
+        
+        # Apply template variables
+        for key, val in self._expressions.items():
+            val = val.expression(names)
+            search = r'\$' + key + r'($|[^a-zA-Z0-9_])'
+            code = re.sub(search, val+r'\1', code)
+
+        # Done
+        if '$' in code:
+            v = parsing.find_template_variables(code)
+            logger.warning('Unsubstituted placeholders in code: %s\n'
+                           '  replacements made: %s' % 
+                           (v, list(self._expressions.keys())))
+        
+        return code + '\n'
+    
+    def definition(self, names):
+        return self._get_replaced_code(names)
+
+    def expression(self, names):
+        return names[self]
+    
+    def _clean_code(self, code):
+        """ Return *code* with indentation and leading/trailing blank lines
+        removed. 
+        """
+        lines = code.split("\n")
+        min_indent = 100
+        for line in lines:
+            if line.strip() != "":
+                indent = len(line) - len(line.lstrip())
+                min_indent = min(indent, min_indent)
+        if min_indent > 0:
+            lines = [line[min_indent:] for line in lines]
+        code = "\n".join(lines)
+        return code
+
+    def __repr__(self):
+        args = ', '.join([' '.join(arg) for arg in self.args])
+        return '<%s "%s %s(%s)" at 0x%x>' % (self.__class__.__name__, 
+                                             self.rtype,
+                                             self.name,
+                                             args,
+                                             id(self))
+
+
+class MainFunction(Function):
+    """ Subclass of Function that allows multiple functions and variables to 
+    be defined in a single code string. The code must contain a main() function
+    definition.
+    """
+    def __init__(self, *args, **kwds):
+        self._chains = {}
+        Function.__init__(self, *args, **kwds)
+    
+    @property
+    def signature(self):
+        return ('main', [], 'void')
+
+    def static_names(self):
+        # parse static variables
+        names = Function.static_names(self)
+        
+        # parse all function names + argument names
+        funcs = parsing.find_functions(self.code)
+        for f in funcs:
+            if f[0] == 'main':
+                continue
+            names.append(f[0])
+            for arg in f[1]:
+                names.append(arg[1])
+        
+        return names
+
+    def add_chain(self, var):
+        """
+        Create a new ChainFunction and attach to $var.
+        """
+        chain = FunctionChain(var, [])
+        self._chains[var] = chain
+        self[var] = chain
+
+    def add_callback(self, hook, func):
+        self._chains[hook].append(func)
+    
+    def remove_callback(self, hook, func):
+        self._chains[hook].remove(func)
+
+
+class Variable(ShaderObject):
+    """ Representation of global shader variable
+    
+    Parameters
+    ----------
+    name : str
+        the name of the variable. This string can also contain the full
+        definition of the variable, e.g. 'uniform vec2 foo'.
+    value : {float, int, tuple, GLObject}
+        If given, vtype and dtype are determined automatically. If a
+        float/int/tuple is given, the variable is a uniform. If a gloo
+        object is given that has a glsl_type property, the variable is
+        an attribute and
+    vtype : {'const', 'uniform', 'attribute', 'varying', 'inout'}
+        The type of variable.
+    dtype : str
+        The data type of the variable, e.g. 'float', 'vec4', 'mat', etc.
+    
+    """
+    def __init__(self, name, value=None, vtype=None, dtype=None):
+        super(Variable, self).__init__()
+        
+        # allow full definition in first argument
+        if ' ' in name:
+            fields = name.split(' ')
+            if len(fields) == 3:
+                vtype, dtype, name = fields
+            elif len(fields) == 4 and fields[0] == 'const':
+                vtype, dtype, name, value = fields
+            else:
+                raise ValueError('Variable specifications given by string must'
+                                 ' be of the form "vtype dtype name" or '
+                                 '"const dtype name value".')
+            
+        if not (isinstance(name, string_types) or name is None):
+            raise TypeError("Variable name must be string or None.")
+        
+        self._state_counter = 0
+        self._name = name
+        self._vtype = vtype
+        self._dtype = dtype
+        self._value = None
+        
+        # If vtype/dtype were given at init, then we will never
+        # try to set these values automatically.
+        self._type_locked = self._vtype is not None and self._dtype is not None
+            
+        if value is not None:
+            self.value = value
+        
+        if self._vtype and self._vtype not in VARIABLE_TYPES:
+            raise ValueError('Not a valid vtype: %r' % self._vtype)
+    
+    @property
+    def name(self):
+        """ The name of this variable.
+        """
+        return self._name
+    
+    @name.setter
+    def name(self, n):
+        # Settable mostly to allow automatic setting of varying names 
+        # See ShaderObject.create()
+        if self._name != n:
+            self._name = n
+            self.changed(code_changed=True)
+    
+    @property
+    def vtype(self):
+        """ The type of variable (const, uniform, attribute, varying or inout).
+        """
+        return self._vtype
+    
+    @property
+    def dtype(self):
+        """ The type of data (float, int, vec, mat, ...).
+        """
+        return self._dtype
+    
+    @property
+    def value(self):
+        """ The value associated with this variable.
+        """
+        return self._value
+    
+    @value.setter
+    def value(self, value):
+        if isinstance(value, (tuple, list)) and 1 < len(value) < 5:
+            vtype = 'uniform'
+            dtype = 'vec%d' % len(value)
+        elif isinstance(value, np.ndarray):
+            if value.ndim == 1 and (1 < len(value) < 5):
+                vtype = 'uniform'
+                dtype = 'vec%d' % len(value)
+            elif value.ndim == 2 and value.shape in ((2, 2), (3, 3), (4, 4)):
+                vtype = 'uniform'
+                dtype = 'mat%d' % value.shape[0]                
+            else:
+                raise ValueError("Cannot make uniform value for %s from array "
+                                 "of shape %s." % (self.name, value.shape))
+        elif np.isscalar(value):
+            vtype = 'uniform'
+            if isinstance(value, (float, np.floating)):
+                dtype = 'float'
+            elif isinstance(value, (int, np.integer)):
+                dtype = 'int'
+            else:
+                raise TypeError("Unknown data type %r for variable %r" % 
+                                (type(value), self))
+        elif getattr(value, 'glsl_type', None) is not None:
+            # Note: hasattr() is broken by design--swallows all exceptions!
+            vtype, dtype = value.glsl_type
+        else:
+            raise TypeError("Unknown data type %r for variable %r" % 
+                            (type(value), self))
+
+        self._value = value
+        self._state_counter += 1
+        
+        if self._type_locked:
+            if dtype != self._dtype or vtype != self._vtype:
+                raise TypeError('Variable is type "%s"; cannot assign value '
+                                '%r.' % (self.dtype, value))
+            return
+            
+        # update vtype/dtype and emit changed event if necessary
+        changed = False
+        if self._dtype != dtype:
+            self._dtype = dtype
+            changed = True
+        if self._vtype != vtype:
+            self._vtype = vtype
+            changed = True
+        if changed:
+            self.changed(code_changed=True, value_changed=True)
+    
+    @property
+    def state_id(self):
+        """Return a unique ID that changes whenever the state of the Variable
+        has changed. This allows ModularProgram to quickly determine whether
+        the value has changed since it was last used."""
+        return id(self), self._state_counter
+
+    def __repr__(self):
+        return ("<%s \"%s %s %s\" at 0x%x>" % (self.__class__.__name__,
+                                               self._vtype, self._dtype, 
+                                               self.name, id(self)))
+    
+    def expression(self, names):
+        return names[self]
+    
+    def definition(self, names):
+        if self.vtype is None:
+            raise RuntimeError("Variable has no vtype: %r" % self)
+        if self.dtype is None:
+            raise RuntimeError("Variable has no dtype: %r" % self)
+        
+        name = names[self]
+        if self.vtype == 'const':
+            return '%s %s %s = %s;' % (self.vtype, self.dtype, name, 
+                                       self.value)
+        else:
+            return '%s %s %s;' % (self.vtype, self.dtype, name)
+
+
+class Varying(Variable):
+    """ Representation of a varying
+    
+    Varyings can inherit their dtype from another Variable, allowing for
+    more flexibility in composing shaders.
+    """
+    def __init__(self, name, dtype=None):
+        self._link = None
+        Variable.__init__(self, name, vtype='varying', dtype=dtype)
+        
+    @property
+    def value(self):
+        """ The value associated with this variable.
+        """
+        return self._value
+    
+    @value.setter
+    def value(self, value):
+        if value is not None:
+            raise TypeError("Cannot assign value directly to varying.")
+    
+    @property
+    def dtype(self):
+        if self._dtype is None:
+            if self._link is None:
+                return None
+            else:
+                return self._link.dtype
+        else:
+            return self._dtype
+
+    def link(self, var):
+        """ Link this Varying to another object from which it will derive its
+        dtype. This method is used internally when assigning an attribute to
+        a varying using syntax ``aFunction[varying] = attr``.
+        """
+        assert self._dtype is not None or hasattr(var, 'dtype')
+        self._link = var
+        self.changed()
+
+
+class Expression(ShaderObject):
+    """ Base class for expressions (ShaderObjects that do not have a
+    definition nor dependencies)
+    """
+    
+    def definition(self, names):
+        # expressions are declared inline.
+        return None
+
+
+class TextExpression(Expression):
+    """ Plain GLSL text to insert inline
+    """
+    
+    def __init__(self, text):
+        super(TextExpression, self).__init__()
+        if not isinstance(text, string_types):
+            raise TypeError("Argument must be string.")
+        self._text = text
+    
+    def __repr__(self):
+        return '<TextExpression %r for at 0x%x>' % (self.text, id(self))
+    
+    def expression(self, names=None):
+        return self._text
+    
+    @property
+    def text(self):
+        return self._text
+    
+    @text.setter
+    def text(self, t):
+        self._text = t
+        self.changed()
+
+    def __eq__(self, a):
+        if isinstance(a, TextExpression):
+            return a._text == self._text
+        elif isinstance(a, string_types):
+            return a == self._text
+        else:
+            return False
+    
+    def __hash__(self):
+        return self._text.__hash__()
+
+
+class FunctionCall(Expression):
+    """ Representation of a call to a function
+    
+    Essentially this is container for a Function along with its signature. 
+    """
+    def __init__(self, function, args):
+        super(FunctionCall, self).__init__()
+        
+        if not isinstance(function, Function):
+            raise TypeError('FunctionCall needs a Function')
+        
+        sig_len = len(function.args)
+        if len(args) != sig_len:
+            raise TypeError('Function %s requires %d arguments (got %d)' %
+                            (function.name, sig_len, len(args)))
+        
+        # Ensure all expressions
+        sig = function.args
+        
+        self._function = function
+        
+        # Convert all arguments to ShaderObject, using arg name if possible.
+        self._args = [ShaderObject.create(arg, ref=sig[i][1]) 
+                      for i, arg in enumerate(args)]
+        
+        self._add_dep(function)
+        for arg in self._args:
+            self._add_dep(arg)
+    
+    def __repr__(self):
+        return '<FunctionCall of %r at 0x%x>' % (self.function.name, id(self))
+    
+    @property
+    def function(self):
+        return self._function
+    
+    @property
+    def dtype(self):
+        return self._function.rtype
+    
+    def expression(self, names):
+        str_args = [arg.expression(names) for arg in self._args]
+        args = ', '.join(str_args)
+        fname = self.function.expression(names)
+        return '%s(%s)' % (fname, args)
+
+
+class FunctionChain(Function):
+    """Subclass that generates GLSL code to call Function list in order
+
+    Functions may be called independently, or composed such that the
+    output of each function provides the input to the next.
+
+    Parameters
+    ----------
+    name : str
+        The name of the generated function
+    funcs : list of Functions
+        The list of Functions that will be called by the generated GLSL code.
+
+    Examples
+    --------
+    This creates a function chain:
+
+        >>> func1 = Function('void my_func_1() {}')
+        >>> func2 = Function('void my_func_2() {}')
+        >>> chain = FunctionChain('my_func_chain', [func1, func2])
+
+    If *chain* is included in a ModularProgram, it will generate the following
+    output:
+
+        void my_func_1() {}
+        void my_func_2() {}
+
+        void my_func_chain() {
+            my_func_1();
+            my_func_2();
+        }
+
+    The return type of the generated function is the same as the return type
+    of the last function in the chain. Likewise, the arguments for the
+    generated function are the same as the first function in the chain.
+
+    If the return type is not 'void', then the return value of each function
+    will be used to supply the first input argument of the next function in
+    the chain. For example:
+
+        vec3 my_func_1(vec3 input) {return input + vec3(1, 0, 0);}
+        void my_func_2(vec3 input) {return input + vec3(0, 1, 0);}
+
+        vec3 my_func_chain(vec3 input) {
+            return my_func_2(my_func_1(input));
+        }
+    """
+    def __init__(self, name=None, funcs=()):
+        # bypass Function.__init__ completely.
+        ShaderObject.__init__(self)
+        if not (name is None or isinstance(name, string_types)):
+            raise TypeError("Name argument must be string or None.")
+        self._funcs = []
+        self._code = None
+        self._name = name or "chain"
+        self._args = []
+        self._rtype = 'void'
+        self.functions = funcs
+
+    @property
+    def functions(self):
+        return self._funcs[:]
+
+    @functions.setter
+    def functions(self, funcs):
+        while self._funcs:
+            self.remove(self._funcs[0], update=False)
+        for f in funcs:
+            self.append(f, update=False)
+        self._update()
+
+    @property
+    def signature(self):
+        return self._name, self._args, self._rtype
+
+    def _update(self):
+        funcs = self._funcs
+        if len(funcs) > 0:
+            self._rtype = funcs[-1].rtype
+            self._args = funcs[0].args[:]
+        else:
+            self._rtype = 'void'
+            self._args = []
+        
+        self.changed(code_changed=True)
+
+    @property
+    def template_vars(self):
+        return {}
+
+    def append(self, function, update=True):
+        """ Append a new function to the end of this chain.
+        """
+        self._funcs.append(function)
+        self._add_dep(function)
+        if update:
+            self._update()
+
+    def __setitem__(self, index, func):
+        self._remove_dep(self._funcs[index])
+        self._add_dep(func)
+        self._funcs[index] = func
+        
+        self._update()
+    
+    def __getitem__(self, k):
+        return self.functions[k]
+    
+    def insert(self, index, function, update=True):
+        """ Insert a new function into the chain at *index*.
+        """
+        self._funcs.insert(index, function)
+        self._add_dep(function)
+        if update:
+            self._update()
+
+    def remove(self, function, update=True):
+        """ Remove a function from the chain.
+        """
+        self._funcs.remove(function)
+        self._remove_dep(function)
+        if update:
+            self._update()
+
+    def definition(self, obj_names):
+        name = obj_names[self]
+
+        args = ", ".join(["%s %s" % arg for arg in self.args])
+        code = "%s %s(%s) {\n" % (self.rtype, name, args)
+
+        result_index = 0
+        if len(self.args) == 0:
+            last_rtype = 'void'
+            last_result = ''
+        else:
+            last_rtype, last_result = self.args[0][:2]
+
+        for fn in self._funcs:
+            # Use previous return value as an argument to the next function
+            if last_rtype == 'void':
+                args = ''
+            else:
+                args = last_result
+                if len(fn.args) != 1 or last_rtype != fn.args[0][0]:
+                    raise Exception("Cannot chain output '%s' of function to "
+                                    "input of '%s'" %
+                                    (last_rtype, fn.signature))
+            last_rtype = fn.rtype
+
+            # Store the return value of this function
+            if fn.rtype == 'void':
+                set_str = ''
+            else:
+                result_index += 1
+                result = 'result_%d' % result_index
+                set_str = '%s %s = ' % (fn.rtype, result)
+                last_result = result
+
+            code += "    %s%s(%s);\n" % (set_str, obj_names[fn], args)
+
+        # return the last function's output
+        if self.rtype != 'void':
+            code += "    return result_%d;\n" % result_index
+
+        code += "}\n"
+        return code
+
+    def static_names(self):
+        return []
+
+    def __repr__(self):
+        fn = ",\n                ".join(map(repr, self.functions))
+        return "<FunctionChain [%s] at 0x%x>" % (fn, id(self))
diff --git a/vispy/scene/shaders/parsing.py b/vispy/scene/shaders/parsing.py
new file mode 100644
index 0000000..00ba32b
--- /dev/null
+++ b/vispy/scene/shaders/parsing.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import re
+
+# regular expressions for parsing GLSL
+re_type = r'(?:void|int|float|vec2|vec3|vec4|mat2|mat3|mat4)'
+re_identifier = r'(?:[a-zA-Z_][\w_]*)'
+
+# variable qualifiers
+re_qualifier = r'(const|uniform|attribute|varying)'
+
+# template variables like
+#     $func_name
+re_template_var = (r"(?:(?:\$" + re_identifier + ")|(?:\$\{"
+                   + re_identifier + "\}))")
+
+# function names may be either identifier or template var
+re_func_name = r"(" + re_identifier + "|" + re_template_var + ")"
+
+# type and identifier like "vec4 var_name"
+re_declaration = "(?:(" + re_type + ")\s+(" + re_identifier + "))"
+
+# qualifier, type, and identifier like "uniform vec4 var_name"
+# qualifier is optional.
+# may include multiple names like "attribute float x, y, z"
+re_prog_var_declaration = ("(?:" + re_qualifier + "?\s*(" + re_type +
+                           ")\s+(" + re_identifier + "(\s*,\s*(" +
+                           re_identifier + "))*))")
+
+# list of variable declarations like "vec4 var_name, float other_var_name"
+re_arg_list = "(" + re_declaration + "(?:,\s*" + re_declaration + ")*)?"
+
+# function declaration like "vec4 function_name(float x, float y)"
+re_func_decl = ("(" + re_type + ")\s+" + re_func_name + "\s*\((void|" +
+                re_arg_list + ")\)")
+
+# anonymous variable declarations may or may not include a name:
+#  "vec4" or "vec4 var_name"
+re_anon_decl = "(?:(" + re_type + ")(?:\s+" + re_identifier + ")?)"
+
+# list of anonymous declarations
+re_anon_arg_list = "(" + re_anon_decl + "(?:,\s*" + re_anon_decl + ")*)?"
+
+# function prototype declaration like
+#    "vec4 function_name(float, float);"
+re_func_prot = ("(" + re_type + ")\s+" + re_func_name + "\((void|" +
+                re_anon_arg_list + ")\)\s*;")
+
+
+def parse_function_signature(code):
+    """
+    Return the name, arguments, and return type of the first function
+    definition found in *code*. Arguments are returned as [(type, name), ...].
+    """
+    m = re.search("^\s*" + re_func_decl + "\s*{", code, re.M)
+    if m is None:
+        print(code)
+        raise Exception("Failed to parse function signature. "
+                        "Full code is printed above.")
+    rtype, name, args = m.groups()[:3]
+    if args == 'void' or args.strip() == '':
+        args = []
+    else:
+        args = [tuple(arg.strip().split(' ')) for arg in args.split(',')]
+    return name, args, rtype
+
+
+def find_functions(code):
+    """
+    Return a list of (name, arguments, return type) for all function 
+    definition2 found in *code*. Arguments are returned as [(type, name), ...].
+    """
+    regex = "^\s*" + re_func_decl + "\s*{"
+    
+    funcs = []
+    while True:
+        m = re.search(regex, code, re.M)
+        if m is None:
+            return funcs
+        
+        rtype, name, args = m.groups()[:3]
+        if args == 'void' or args.strip() == '':
+            args = []
+        else:
+            args = [tuple(arg.strip().split(' ')) for arg in args.split(',')]
+        funcs.append((name, args, rtype))
+        
+        code = code[m.end():]
+
+
+def find_prototypes(code):
+    """
+    Return a list of signatures for each function prototype declared in *code*.
+    Format is [(name, [args], rtype), ...].
+    """
+
+    prots = []
+    lines = code.split('\n')
+    for line in lines:
+        m = re.match("\s*" + re_func_prot, line)
+        if m is not None:
+            rtype, name, args = m.groups()[:3]
+            if args == 'void' or args.strip() == '':
+                args = []
+            else:
+                args = [tuple(arg.strip().split(' '))
+                        for arg in args.split(',')]
+            prots.append((name, args, rtype))
+
+    return prots
+
+
+def find_program_variables(code):
+    """
+    Return a dict describing program variables::
+
+        {'var_name': ('uniform|attribute|varying', type), ...}
+
+    """
+    vars = {}
+    lines = code.split('\n')
+    for line in lines:
+        m = re.match(r"\s*" + re_prog_var_declaration + r"\s*(=|;)", line)
+        if m is not None:
+            vtype, dtype, names = m.groups()[:3]
+            for name in names.split(','):
+                vars[name.strip()] = (vtype, dtype)
+    return vars
+
+
+def find_template_variables(code):
+    """
+    Return a list of template variables found in *code*.
+
+    """
+    return re.findall(re_template_var, code)
diff --git a/vispy/scene/shaders/program.py b/vispy/scene/shaders/program.py
new file mode 100644
index 0000000..f41afb1
--- /dev/null
+++ b/vispy/scene/shaders/program.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division, print_function
+
+from ...gloo import Program
+from ...util import logger
+from ...util.event import EventEmitter
+from ...ext.six import string_types  # noqa
+from .function import MainFunction, Variable
+from .compiler import Compiler
+
+
+class ModularProgram(Program):
+    """
+    Shader program using Function instances as basis for its shaders.
+    
+    Automatically rebuilds program when functions have changed and uploads 
+    program variables.
+    """
+    def __init__(self, vcode, fcode):
+        Program.__init__(self, '', '')
+        
+        self.changed = EventEmitter(source=self, type='program_change')
+        
+        self.vert = MainFunction(vcode)
+        self.frag = MainFunction(fcode)
+        self.vert.changed.connect(self._source_changed)
+        self.frag.changed.connect(self._source_changed)
+        
+        # Cache state of Variables so we know which ones require update
+        self._variable_state = {}
+        
+        self._need_build = True
+
+    def prepare(self):
+        """ Prepare the Program so we can set attributes and uniforms.
+        """
+        # TEMP function to fix sync issues for now
+        self._create()
+        if self._need_build:
+            self._build()
+            self._need_build = False
+    
+    def _source_changed(self, ev):
+        logger.debug("ModularProgram source changed: %s" % self)
+        if ev.code_changed:
+            self._need_build = True
+        self.changed()
+        
+    def _build(self):
+        logger.debug("Rebuild ModularProgram: %s" % self)
+        self.compiler = Compiler(vert=self.vert, frag=self.frag)
+        code = self.compiler.compile()
+        self.shaders[0].code = code['vert']
+        self.shaders[1].code = code['frag']
+        
+        logger.debug('==== Vertex Shader ====\n\n' + code['vert'] + "\n")
+        logger.debug('==== Fragment shader ====\n\n' + code['frag'] + "\n")
+        
+        self._create_variables()  # force update
+        self._variable_state = {}
+        
+        # and continue.
+        super(ModularProgram, self)._build()
+
+    def _activate_variables(self):
+        # set all variables
+        settable_vars = 'attribute', 'uniform'
+        logger.debug("Apply variables:")
+        deps = self.vert.dependencies() + self.frag.dependencies()
+        for dep in deps:
+            if not isinstance(dep, Variable) or dep.vtype not in settable_vars:
+                continue
+            name = self.compiler[dep]
+            logger.debug("    %s = %s", name, dep.value)
+            state_id = dep.state_id
+            if self._variable_state.get(name, None) != state_id:
+                self[name] = dep.value
+                self._variable_state[name] = state_id
+        
+        super(ModularProgram, self)._activate_variables()        
diff --git a/vispy/shaders/__init__.py b/vispy/scene/shaders/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/scene/shaders/tests/__init__.py
diff --git a/vispy/scene/shaders/tests/test_function.py b/vispy/scene/shaders/tests/test_function.py
new file mode 100644
index 0000000..fd87aab
--- /dev/null
+++ b/vispy/scene/shaders/tests/test_function.py
@@ -0,0 +1,450 @@
+from vispy.scene.shaders.function import (Function, Variable, Varying,
+                                          MainFunction, FunctionChain)
+
+# Users normally don't need these, but I want to test them
+from vispy.scene.shaders.function import FunctionCall, TextExpression
+
+from nose.tools import assert_raises, assert_equal, assert_not_equal  # noqa
+from vispy.testing import assert_in, assert_not_in, assert_is  # noqa
+
+
+## Define some snippets
+
+transformScale = Function("""
+vec4 transform_scale(vec4 pos)
+{
+    pos.xyz *= $scale;
+    return pos;
+}
+""")
+
+transformZOffset = Function("""
+vec4 transform_zoffset(vec4 pos)
+{
+    pos.z += $offset;
+    return pos;
+}
+""")
+
+vert_template = Function("""
+void main(void)
+{   
+    int nlights = $nlights;
+    vec4 pos = $position;
+    pos += $correction;
+    gl_Position = $endtransform(pos);
+}
+
+""")
+
+frag_template = Function("""
+void main(void)
+{
+    gl_Fragcolor = $color;
+}
+
+""")
+
+data = 'just some dummy variable, Function is agnostic about this'
+
+
+## Examples
+
+def test_example1():
+    """ Just a few simple compositions.
+    """
+    
+    # Get function objects. Generate random name for transforms
+    code = Function(vert_template)
+    t1 = Function(transformScale)
+    t2 = Function(transformZOffset)
+    t3 = Function(transformScale)
+    
+    # We need to create a variable in order to use it in two places
+    pos = Variable('attribute vec4 a_position')
+    
+    # Compose everything together
+    code['position'] = t1(t2(pos))
+    code['correction'] = t1(pos)  # Look, we use t1 again, different sig
+    code['endtransform'] = t3  # function pointer rather than function call
+    code['nlights'] = '4'
+    t1['scale'] = t2
+    t3['scale'] = (3.0, 4.0, 5.0)
+    t2['offset'] = '1.0'
+    
+    code2 = Function(frag_template)
+    code2['color'] = Varying('v_position')
+    
+    code['gl_PointSize'] = '3.0'
+    code[code2['color']] = pos
+    print(code)
+
+
+def test_example2():
+    """ Demonstrate how a transform would work.
+    """
+    
+    vert_template = Function("""
+    void main(void)
+    {
+        gl_Position = $position;
+    }
+    """)
+    
+    transformScale = Function("""
+    vec4 transform_scale(vec4 pos)
+    {
+        pos.xyz *= $scale;
+        return pos;
+    }
+    """)
+    
+    class Transform(object):
+        def __init__(self):
+            # Equivalent methods to create new function object
+            self.func = Function(transformScale)
+            self.func['scale'] = 'uniform float'
+            #self.func = Function(transformScale)
+        
+        def set_scale(self, scale):
+            self.func['scale'].value = scale
+    
+    transforms = [Transform(), Transform(), Transform()]
+    
+    code = Function(vert_template)
+    ob = Variable('attribute vec3 a_position')
+    for trans in transforms:
+        ob = trans.func(ob)
+    code['position'] = ob
+    print(code)
+
+## Tests
+
+
+def test_TextExpression():
+    exp = TextExpression('foo bar')
+    assert_equal('foo bar', exp.expression(None))
+    assert_equal(None, exp.definition(None))
+    assert_raises(TypeError, TextExpression, 4)
+
+
+def test_FunctionCall():
+    fun = Function(transformScale)
+    fun['scale'] = '1.0'
+    fun2 = Function(transformZOffset)
+    
+    # No args
+    assert_raises(TypeError, fun)  # need 1 arg
+    assert_raises(TypeError, fun, 1, 2)  # need 1 arg
+    call = fun('x')
+    # Test repr
+    exp = call.expression({fun: 'y'})
+    assert_equal(exp, 'y(x)')
+    # Test sig
+    assert len(call._args) == 1
+    # Test dependencies
+    assert_in(fun, call.dependencies())
+    assert_in(call._args[0], call.dependencies())
+    
+    # More args
+    call = fun(fun2('foo'))
+    # Test repr
+    exp = call.expression({fun: 'y', fun2: 'z'})
+    assert_in('y(z(', exp)
+    # Test sig
+    assert len(call._args) == 1
+    call2 = call._args[0]
+    assert len(call2._args) == 1
+    # Test dependencies
+    assert_in(fun, call.dependencies())
+    assert_in(call._args[0], call.dependencies())
+    assert_in(fun2, call.dependencies())
+    assert_in(call2._args[0], call.dependencies())
+
+
+def test_Variable():
+    
+    # Test init fail
+    assert_raises(TypeError, Variable)  # no args
+    assert_raises(TypeError, Variable, 3)  # wrong type
+    assert_raises(TypeError, Variable, "name", "str")  # wrong type
+    assert_raises(ValueError, Variable, 'bla bla')  # need correct vtype
+    assert_raises(ValueError, Variable, 'uniform b l a')  # too many
+    
+    # Test init success
+    var = Variable('uniform float bla')  # Finally
+    assert_equal(var.name, 'bla')
+    assert_equal(var.dtype, 'float')
+    assert_equal(var.vtype, 'uniform')
+    assert var.value is None
+    
+    # test assign new value
+    var.value = 10.
+    assert_equal(var.dtype, 'float')  # type is locked; won't change
+    
+    # test name-only init
+    var = Variable('bla')  # Finally
+    assert_equal(var.name, 'bla')
+    assert_equal(var.dtype, None)
+    assert_equal(var.vtype, None)
+    assert var.value is None
+    
+    # test assign new value
+    var.value = 10
+    assert_equal(var.dtype, 'int')
+    assert_equal(var.vtype, 'uniform')
+    assert_equal(var.value, 10)
+    
+    # test init with value
+    var = Variable('bla', (1, 2, 3))  # Also valid
+    assert_equal(var.name, 'bla')
+    assert_equal(var.dtype, 'vec3')
+    assert_equal(var.vtype, 'uniform')
+    assert_equal(var.value, (1, 2, 3))
+    
+    # Test value
+    #var = Variable('uniform float bla', data)  # Also valid
+    #assert_equal(var.value, data)
+    #var.value = 3
+    #assert_equal(var.value, 3)
+    
+    # Test repr
+    var = Variable('uniform float bla')
+    assert_in('uniform float bla', var.compile())
+    
+    # Test injection, definition, dependencies
+    assert_equal(var.expression({var: 'xxx'}), 'xxx')
+    assert_equal(var.definition({var: 'xxx'}), 'uniform float xxx;')
+    assert_in(var, var.dependencies())
+    
+    # Renaming
+    var = Variable('uniform float bla')
+    assert_equal(var.name, 'bla')
+    var.name = 'foo'
+    assert_equal(var.name, 'foo')
+    
+
+def test_function_basics():
+    
+    # Test init fail
+    assert_raises(TypeError, Function)  # no args
+    assert_raises(ValueError, Function, 3)  # need string
+
+    # Test init success 1
+    fun = Function('void main(){}')
+    assert_equal(fun.name, 'main')
+    assert len(fun.template_vars) == 0
+    
+    # Test init success with template vars
+    fun = Function('void main(){$foo; $bar;}')
+    assert_equal(fun.name, 'main')
+    assert len(fun.template_vars) == 2
+    assert_in('foo', fun.template_vars)
+    assert_in('bar', fun.template_vars)
+    
+    # Test setting verbatim expressions
+    assert_raises(KeyError, fun.__setitem__, 'bla', '33')  # no such template
+    fun['foo'] = '33'
+    fun['bar'] = 'bla bla'
+    assert_is(type(fun['foo']), TextExpression)
+    assert_equal(fun['foo'].expression(None), '33')
+    assert_is(type(fun['bar']), TextExpression)
+    assert_equal(fun['bar'].expression(None), 'bla bla')
+    
+    # Test setting call expressions
+    fun = Function('void main(){\n$foo;\n$bar;\n$spam(XX);\n$eggs(YY);\n}')
+    trans = Function('float transform_scale(float x) {return x+1.0;}')
+    assert_raises(TypeError, trans)  # requires 1 arg 
+    assert_raises(TypeError, trans, '1', '2')
+    fun['foo'] = trans('2')
+    fun['bar'] = trans('3')
+    fun['spam'] = trans
+    fun['eggs'] = trans
+    #
+    for name in ['foo', 'bar']:
+        assert_is(type(fun[name]), FunctionCall)
+        assert_equal(fun[name].function, trans)
+        assert_in(trans, fun.dependencies())
+    for name in ['spam', 'eggs']:
+        assert_equal(fun[name], trans)
+        
+    #
+    text = fun.compile()
+    assert_in('\ntransform_scale(2);\n', text)
+    assert_in('\ntransform_scale(3);\n', text)
+    assert_in('\ntransform_scale(XX);\n', text)
+    assert_in('\ntransform_scale(YY);\n', text)
+    
+    # Test variable expressions
+    fun = Function('void main(){$foo; $bar;}')
+    fun['foo'] = Variable('uniform float bla')
+    fun['bar'] = Variable('attribute float bla')
+    assert_is(type(fun['foo']), Variable)
+    assert_is(type(fun['bar']), Variable)
+    assert_in(fun['foo'], fun.dependencies())
+    assert_in(fun['bar'], fun.dependencies())
+    
+    # Test special variables
+    fun = Function('void main(){$foo; $bar;}')
+    variable = Variable('attribute vec3 v_pos')
+    varying = Variable('varying vec3 color')
+    # These do not work due to index
+    assert_raises(TypeError, fun.__setitem__, 3, 3)  # not a string
+    assert_raises(KeyError, fun.__setitem__, 'xxx', 3)  # unknown template var
+    assert_raises(TypeError, fun.__setitem__, variable, 3)  # only varyings
+    # These work
+    fun['gl_PointSize'] = '3.0'
+    fun[varying] = variable
+    # And getting works
+    assert_equal(fun['gl_PointSize'].text, '3.0')
+    assert_equal(fun[varying], variable)
+
+
+def test_function_changed():
+    ch = []
+    
+    def on_change(event):
+        ch.append(event.source)
+        
+    def assert_changed(*objs):
+        assert set(ch) == set(objs)
+        while ch:
+            ch.pop()
+        
+    fun1 = Function('void main(){$var1; $var2;}')
+    fun1.changed.connect(on_change)
+    fun1['var1'] = 'x'
+    fun1['var2'] = 'y'
+    assert_changed(fun1)
+    
+    fun1['var1'] = 'z'
+    assert_changed(fun1)
+    
+    # same value; should result in no change events
+    fun1['var1'] = 'z'
+    assert_changed()
+    
+    fun1['var1'] = 0.5
+    var1 = fun1['var1']
+    var1.changed.connect(on_change)
+    assert_changed(fun1)
+    
+    var1.name = 'xxx'
+    assert_changed(fun1, var1)
+    
+    # changing type requires code change
+    var1.value = 7
+    assert_changed(fun1, var1)
+
+    # changing value (but not type) requires no code changes
+    var1.value = 6
+    assert_changed()
+
+    # test variable disconnect
+    fun1['var1'] = Variable('var1', 7)
+    var2 = fun1['var1']
+    var2.changed.connect(on_change)
+    #assert_changed(fun1)
+    # var2 is now connected
+    var2.value = (1, 2, 3, 4)
+    assert_changed(fun1, var2)
+    # ..but var1 no longer triggers fun1.changed
+    assert_changed()
+    var1.value = 0.5
+    assert_changed(var1)
+
+    # test expressions
+    fun2 = Function('float fn(float x){return $var1 + x;}')
+    fun3 = Function('float fn(float x){return $var1 + x;}')
+    exp1 = fun2(fun3(0.5))
+    fun1['var2'] = exp1
+    assert_changed(fun1)
+    
+    fun2.changed.connect(on_change)
+    fun3.changed.connect(on_change)
+    exp1.changed.connect(on_change)
+    
+    fun2['var1'] = 'x'
+    assert_changed(fun1, fun2, exp1)
+    
+    fun3['var1'] = 'x'
+    assert_changed(fun1, fun3, exp1)
+
+    # test disconnect
+    fun1['var2'] = fun2
+    assert_changed(fun1)
+    # triggers change
+    fun2['var1'] = 0.9
+    assert_changed(fun1, fun2, exp1)
+    # no longer triggers change
+    fun3['var1'] = 0.9
+    assert_changed(fun3, exp1)
+    
+    
+def test_FunctionChain():
+    
+    f1 = Function("void f1(){}")
+    f2 = Function("void f2(){}")
+    f3 = Function("float f3(vec3 x){}")
+    f4 = Function("vec3 f4(vec3 y){}")
+    f5 = Function("vec3 f5(vec4 z){}")
+    
+    ch = FunctionChain('chain', [f1, f2])
+    assert ch.name == 'chain'
+    assert ch.args == []
+    assert ch.rtype == 'void'
+    
+    assert_in('f1', ch.compile())
+    assert_in('f2', ch.compile())
+    
+    ch.remove(f2)
+    assert_not_in('f2', ch.compile())
+
+    ch.append(f2)
+    assert_in('f2', ch.compile())
+
+    ch = FunctionChain(funcs=[f5, f4, f3])
+    assert_equal('float', ch.rtype)
+    assert_equal([('vec4', 'z')], ch.args)
+    assert_in('f3', ch.compile())
+    assert_in('f4', ch.compile())
+    assert_in('f5', ch.compile())
+    assert_in(f3, ch.dependencies())
+    assert_in(f4, ch.dependencies())
+    assert_in(f5, ch.dependencies())
+
+
+def test_MainFunction():
+    code = """
+    const float pi = 3.0;  // close enough.
+    
+    vec4 rotate(vec4 pos) {
+        return pos;  // just kidding.
+    }
+    
+    attribute mat4 m_transform;
+    attribute vec4 a_pos;
+    void main() {
+        gl_Position = m_transform * a_pos;
+    }
+    """
+    
+    mf = MainFunction(code)
+    
+    assert mf.name == 'main'
+    assert mf.rtype == 'void'
+    assert len(mf.args) == 0
+    sn = set(mf.static_names())
+    assert sn == set(['pi', 'rotate', 'pos', 'm_transform', 'a_pos'])
+    
+
+if __name__ == '__main__':
+    for key in [key for key in globals()]:
+        if key.startswith('test_'):
+            func = globals()[key]
+            print('running', func.__name__)
+            func()
+    
+    # Uncomment to run example
+    print('='*80)
+    test_example1()
diff --git a/vispy/scene/shaders/tests/test_parsing.py b/vispy/scene/shaders/tests/test_parsing.py
new file mode 100644
index 0000000..81731bb
--- /dev/null
+++ b/vispy/scene/shaders/tests/test_parsing.py
@@ -0,0 +1,49 @@
+import re
+from vispy.scene.shaders.parsing import re_identifier, find_program_variables
+
+
+def test_identifier():
+    assert(re.match('('+re_identifier+')', 'Ax2_d3__7').groups()[0]
+           == 'Ax2_d3__7')
+    assert(re.match('('+re_identifier+')', '_Ax2_d3__7').groups()[0]
+           == '_Ax2_d3__7')
+    assert(re.match(re_identifier, '7Ax2_d3__7') is None)
+    assert(re.match('('+re_identifier+')', 'x,y').groups()[0] == 'x')
+    assert(re.match('('+re_identifier+')', 'x y').groups()[0] == 'x')
+
+
+def test_find_variables():
+    code = """
+    float x;
+    float y, z;
+    int w,v,u;
+    junk
+    vec4 t = vec4(0, 0, 1, 1);
+    junk junk junk;
+    uniform vec2 s;
+    attribute float r,q;
+    const mat4 p;
+    void main() {
+        vec2 float undetectable;
+    }
+    """
+    
+    expect = dict(
+        x=(None, 'float'),
+        y=(None, 'float'),
+        z=(None, 'float'),
+        w=(None, 'int'),
+        v=(None, 'int'),
+        u=(None, 'int'),
+        t=(None, 'vec4'),
+        s=('uniform', 'vec2'),
+        q=('attribute', 'float'),
+        r=('attribute', 'float'),
+        p=('const', 'mat4'),
+    )
+
+    vars = find_program_variables(code)
+    for k in expect:
+        assert expect[k] == vars.pop(k)
+        
+    assert len(vars) == 0
diff --git a/vispy/scene/subscene.py b/vispy/scene/subscene.py
new file mode 100644
index 0000000..0ca180b
--- /dev/null
+++ b/vispy/scene/subscene.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from .entity import Entity
+from .systems import DrawingSystem, MouseInputSystem
+
+
+class SubScene(Entity):
+    """ A subscene with entities.
+
+    A subscene can be a child of a Canvas or a ViewBox. It is a
+    placeholder for the transform to make the sub scene appear as
+    intended. It's transform cannot be mannually set, but its set
+    automatically and is based on three components:
+
+      * Viewbox transform: a scale and translation to make the subscene
+        be displayed in the boundaries of the viewbox.
+      * Projection transform: the camera projection, e.g. perspective
+      * position transform: the inverse of the transform from this
+        subscene to the camera. This takes care of position and
+        orientation of the view.
+
+    TODO: should camera, lights, etc. be properties of the subscene or the
+    viewbox? I think of the scene. In that way canvas.scene can simply
+    be a subscene.
+    """
+
+    def __init__(self, **kwargs):
+        Entity.__init__(self, **kwargs)
+
+        # Initialize systems
+        self._systems = {}
+        self._systems['draw'] = DrawingSystem()
+        self._systems['mouse'] = MouseInputSystem()
+    
+    def draw(self, event):
+        # Invoke our drawing system
+        self.process_system(event, 'draw') 
+    
+    def _process_mouse_event(self, event):
+        self.process_system(event, 'mouse') 
+
+    def process_system(self, event, system_name):
+        """ Process a system.
+        """
+        self._systems[system_name].process(event, self)
diff --git a/vispy/scene/systems.py b/vispy/scene/systems.py
new file mode 100644
index 0000000..282be9c
--- /dev/null
+++ b/vispy/scene/systems.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import sys
+
+from .visuals.visual import Visual
+from ..util.logs import logger, _handle_exception
+
+
+class DrawingSystem(object):
+    """ Simple implementation of a drawing engine. There is one system
+    per viewbox.
+
+    """
+    def process(self, event, subscene):
+        # Iterate over entities
+        #assert isinstance(subscene, SubScene)  # LC: allow any part of the
+                                                #     scene to be drawn
+        self._process_entity(event, subscene, force_recurse=True)
+
+    def _process_entity(self, event, entity, force_recurse=False):
+        event.canvas._process_entity_count += 1
+
+        if isinstance(entity, Visual):
+            try:
+                entity.draw(event)
+            except Exception:
+                # get traceback and store (so we can do postmortem
+                # debugging)
+                _handle_exception(False, 'reminders', self, entity=entity)
+
+        # Processs children; recurse.
+        # Do not go into subscenes (SubScene.draw processes the subscene)
+        
+        # import here to break import cycle.
+        # (LC: we should be able to remove this
+        # check entirely.)
+        from .subscene import SubScene
+        
+        if force_recurse or not isinstance(entity, SubScene):
+            for sub_entity in entity.children:
+                event.push_entity(sub_entity)
+                try:
+                    self._process_entity(event, sub_entity)
+                finally:
+                    event.pop_entity()
+
+
+class MouseInputSystem(object):
+    def process(self, event, subscene):
+        # For simplicity, this system delivers the event to each entity
+        # in the scenegraph, except for widgets that are not under the 
+        # press_event. 
+        # TODO: 
+        #  1. This eventually should be replaced with a picking system.
+        #  2. We also need to ensure that if one entity accepts a press 
+        #     event, it will also receive all subsequent mouse events
+        #     until the button is released.
+        
+        self._process_entity(event, subscene)
+    
+    def _process_entity(self, event, entity):
+        # Push entity and set its total transform
+        #event.push_entity(entity)
+
+        from .widgets.widget import Widget
+        if isinstance(entity, Widget):
+            # widgets are rectangular; easy to do mouse collision 
+            # testing
+            if event.press_event is None:
+                deliver = entity.rect.contains(*event.pos[:2])
+            else:
+                deliver = entity.rect.contains(*event.press_event.pos[:2])
+        else:
+            deliver = True
+                
+        if deliver:
+            for sub_entity in entity.children:
+                event.push_entity(sub_entity)
+                try:
+                    self._process_entity(event, sub_entity)
+                finally:
+                    event.pop_entity()
+                if event.handled:
+                    break
+            if not event.handled:
+                try:
+                    getattr(entity.events, event.type)(event)
+                except Exception:
+                    # 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
+                    del tb  # Get rid of it in this namespace
+                    # Handle
+                    logger.log_exception()
+                    logger.warning("Error handling mouse event for entity %s" %
+                                   entity)
+                    
+        #event.pop_entity()
diff --git a/vispy/shaders/__init__.py b/vispy/scene/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/scene/tests/__init__.py
diff --git a/vispy/scene/transforms/__init__.py b/vispy/scene/transforms/__init__.py
new file mode 100644
index 0000000..a54e1ac
--- /dev/null
+++ b/vispy/scene/transforms/__init__.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Provides classes representing different transform types suitable for
+use with visuals and scenes.
+"""
+
+__all__ = ['NullTransform', 'STTransform', 'AffineTransform',
+           'PerspectiveTransform', 'LogTransform', 'PolarTransform',
+           'ChainTransform']
+
+from .base_transform import BaseTransform  # noqa
+from .linear import (NullTransform, STTransform,  # noqa
+                     AffineTransform,  PerspectiveTransform)  # noqa
+from .nonlinear import LogTransform, PolarTransform  # noqa
+from .chain import ChainTransform  # noqa
+from ._util import arg_to_array, arg_to_vec4, TransformCache  # noqa
+
+
+transform_types = {}
+for o in list(globals().values()):
+    try:
+        if issubclass(o, BaseTransform) and o is not BaseTransform:
+            name = o.__name__[:-len('Transform')].lower()
+            transform_types[name] = o
+    except TypeError:
+        continue
+
+
+def create_transform(type, *args, **kwds):
+    return transform_types[type](*args, **kwds)
diff --git a/vispy/scene/transforms/_util.py b/vispy/scene/transforms/_util.py
new file mode 100644
index 0000000..6124c19
--- /dev/null
+++ b/vispy/scene/transforms/_util.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+
+def arg_to_array(func):
+    """
+    Decorator to convert argument to array.
+    """
+    def fn(self, arg, *args, **kwds):
+        return func(self, np.array(arg), *args, **kwds)
+    return fn
+
+
+def as_vec4(obj, default=(0, 0, 0, 1)):
+    """
+    Convert *obj* to 4-element vector (numpy array with shape[-1] == 4)
+
+    If *obj* has < 4 elements, then new elements are added from *default*.
+    For inputs intended as a position or translation, use default=(0,0,0,1).
+    For inputs intended as scale factors, use default=(1,1,1,1).
+    """
+    obj = np.array(obj)
+
+    # If this is a single vector, reshape to (1, 4)
+    if obj.ndim == 1:
+        obj = obj[np.newaxis, :]
+
+    # For multiple vectors, reshape to (..., 4)
+    if obj.shape[-1] < 4:
+        new = np.empty(obj.shape[:-1] + (4,), dtype=obj.dtype)
+        new[:] = default
+        new[..., :obj.shape[-1]] = obj
+        obj = new
+    elif obj.shape[-1] > 4:
+        raise TypeError("Array shape %s cannot be converted to vec4"
+                        % obj.shape)
+
+    return obj
+
+
+def arg_to_vec4(func):
+    """
+    Decorator for converting argument to vec4 format suitable for 4x4 matrix
+    multiplication.
+
+    [x, y]      =>  [[x, y, 0, 1]]
+
+    [x, y, z]   =>  [[x, y, z, 1]]
+
+    [[x1, y1],      [[x1, y1, 0, 1],
+     [x2, y2],  =>   [x2, y2, 0, 1],
+     [x3, y3]]       [x3, y3, 0, 1]]
+
+    If 1D input is provided, then the return value will be flattened.
+    Accepts input of any dimension, as long as shape[-1] <= 4
+
+    Alternatively, any class may define its own transform conversion interface
+    by defining a _transform_in() method that returns an array with shape
+    (.., 4), and a _transform_out() method that accepts the same array shape
+    and returns a new (mapped) object.
+
+    """
+    def fn(self, arg, *args, **kwds):
+        if isinstance(arg, (tuple, list, np.ndarray)):
+            arg = np.array(arg)
+            flatten = arg.ndim == 1
+            arg = as_vec4(arg)
+
+            ret = func(self, arg, *args, **kwds)
+            if flatten and ret is not None:
+                return ret.flatten()
+            return ret
+        elif hasattr(arg, '_transform_in'):
+            arr = arg._transform_in()
+            ret = func(self, arr, *args, **kwds)
+            return arg._transform_out(ret)
+        else:
+            raise TypeError("Cannot convert argument to 4D vector: %s" % arg)
+    return fn
+
+
+class TransformCache(object):
+    """ Utility class for managing a cache of transforms that map along an
+    Entity path.
+
+    This is an LRU cache; items are removed if they are not accessed after
+    *max_age* calls to roll().
+
+    Notes
+    -----
+    This class is used by SceneCanvas to ensure that ChainTransform instances
+    are re-used across calls to draw_visual(). SceneCanvas creates one
+    TransformCache instance for each top-level visual drawn, and calls
+    roll() on each cache before drawing, which removes from the cache any
+    transforms that were not accessed during the last draw cycle.
+    """
+    def __init__(self, max_age=1):
+        self._cache = {}  # maps {key: [age, transform]}
+        self.max_age = max_age
+
+    def get(self, path):
+        """ Get a transform from the cache that maps along *path*, which must
+        be a list of Transforms to apply in reverse order (last transform is
+        applied first).
+
+        Accessed items have their age reset to 0.
+        """
+        key = tuple(map(id, path))
+        item = self._cache.get(key, None)
+        if item is None:
+            item = [0, self._create(path)]
+            self._cache[key] = item
+        item[0] = 0  # reset age for this item
+
+        # make sure the chain is up to date
+        #tr = item[1]
+        #for i, entity in enumerate(path[1:]):
+        #    if tr.transforms[i] is not entity.transform:
+        #        tr[i] = entity.transform
+
+        return item[1]
+
+    def _create(self, path):
+        # import here to avoid import cycle
+        from .chain import ChainTransform
+        return ChainTransform(path)
+
+    def roll(self):
+        """ Increase the age of all items in the cache by 1. Items whose age
+        is greater than self.max_age will be removed from the cache.
+        """
+        rem = []
+        for key, item in self._cache.items():
+            if item[0] > self.max_age:
+                rem.append(key)
+            item[0] += 1
+
+        for key in rem:
+            del self._cache[key]
diff --git a/vispy/scene/transforms/base_transform.py b/vispy/scene/transforms/base_transform.py
new file mode 100644
index 0000000..536fa82
--- /dev/null
+++ b/vispy/scene/transforms/base_transform.py
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from ..shaders import Function
+from ...util.event import EventEmitter
+
+"""
+API Issues to work out:
+
+  - AffineTransform and STTransform both have 'scale' and 'translate'
+    attributes, but they are used in very different ways. It would be nice
+    to keep this consistent, but how?
+
+  - Need a transform.map_rect function that returns the bounding rectangle of
+    a rect after transformation. Non-linear transforms might need to work
+    harder at this, but we can provide a default implementation that
+    works by mapping a selection of points across a grid within the original
+    rect.
+"""
+
+
+class BaseTransform(object):
+    """
+    BaseTransform is a base class that defines a pair of complementary
+    coordinate mapping functions in both python and GLSL.
+
+    All BaseTransform subclasses define map() and imap() methods that map
+    an object through the forward or inverse transformation, respectively.
+
+    The two class variables glsl_map and glsl_imap are instances of
+    shaders.Function that define the forward- and inverse-mapping GLSL
+    function code.
+
+    Optionally, an inverse() method returns a new transform performing the
+    inverse mapping.
+
+    Note that although all classes should define both map() and imap(), it
+    is not necessarily the case that imap(map(x)) == x; there may be instances
+    where the inverse mapping is ambiguous or otherwise meaningless.
+
+    """
+    glsl_map = None  # Must be GLSL code
+    glsl_imap = None
+
+    # Flags used to describe the transformation. Subclasses should define each
+    # as True or False.
+    # (usually used for making optimization decisions)
+
+    # If True, then for any 3 colinear points, the
+    # transformed points will also be colinear.
+    Linear = None
+
+    # The transformation's effect on one axis is independent
+    # of the input position along any other axis.
+    Orthogonal = None
+
+    # If True, then the distance between two points is the
+    # same as the distance between the transformed points.
+    NonScaling = None
+
+    # Scale factors are applied equally to all axes.
+    Isometric = None
+
+    def __init__(self):
+        self._inverse = None
+        self.changed = EventEmitter(source=self, type='transform_changed')
+        if self.glsl_map is not None:
+            self._shader_map = Function(self.glsl_map)
+        if self.glsl_imap is not None:
+            self._shader_imap = Function(self.glsl_imap)
+
+    def map(self, obj):
+        """
+        Return *obj* mapped through the forward transformation.
+
+        Parameters:
+            obj : tuple (x,y) or (x,y,z)
+                  array with shape (..., 2) or (..., 3)
+        """
+        raise NotImplementedError()
+
+    def imap(self, obj):
+        """
+        Return *obj* mapped through the inverse transformation.
+
+        Parameters:
+            obj : tuple (x,y) or (x,y,z)
+                  array with shape (..., 2) or (..., 3)
+        """
+        raise NotImplementedError()
+
+    @property
+    def inverse(self):
+        """ The inverse of this transform. 
+        """
+        if self._inverse is None:
+            self._inverse = InverseTransform(self)
+        return self._inverse
+
+    def shader_map(self):
+        """
+        Return a shader Function that accepts only a single vec4 argument
+        and defines new attributes / uniforms supplying the Function with
+        any static input.
+        """
+        return self._shader_map
+
+    def shader_imap(self):
+        """
+        see shader_map.
+        """
+        return self._shader_imap
+
+    def update(self):
+        """
+        Called to inform any listeners that this transform has changed.
+        """
+        self.changed()
+
+    def __mul__(self, tr):
+        """
+        Transform multiplication returns a new transform that is equivalent to
+        the two operands performed in series.
+
+        By default, multiplying two Transforms `A * B` will return
+        ChainTransform([A, B]). Subclasses may redefine this operation to
+        return more optimized results.
+
+        To ensure that both operands have a chance to simplify the operation,
+        all subclasses should follow the same procedure. For `A * B`:
+
+        1. A.__mul__(B) attempts to generate an optimized transform product.
+        2. If that fails, it must:
+
+               * return super(A).__mul__(B) OR
+               * return NotImplemented if the superclass would return an
+                 invalid result.
+
+        3. When BaseTransform.__mul__(A, B) is called, it returns 
+           NotImplemented, which causes B.__rmul__(A) to be invoked.
+        4. B.__rmul__(A) attempts to generate an optimized transform product.
+        5. If that fails, it must:
+
+               * return super(B).__rmul__(A) OR
+               * return ChainTransform([B, A]) if the superclass would return
+                 an invalid result.
+
+        6. When BaseTransform.__rmul__(B, A) is called, ChainTransform([A, B])
+           is returned.
+        """
+        # switch to __rmul__ attempts.
+        # Don't use the "return NotImplemted" trick, because that won't work if
+        # self and tr are of the same type.
+        return tr.__rmul__(self)
+
+    def __rmul__(self, tr):
+        return ChainTransform([tr, self])
+
+    def __repr__(self):
+        return "<%s at 0x%x>" % (self.__class__.__name__, id(self))
+
+
+class InverseTransform(BaseTransform):
+    def __init__(self, transform):
+        self._transform = transform
+        self.shader_map = self._transform.shader_imap
+        self.shader_imap = self._transform.shader_map
+        self.map = self._transform.imap
+        self.imap = self._transform.map
+        
+    @property
+    def inverse(self):
+        return self._transform
+    
+    def __repr__(self):
+        return ("<Inverse of %r>" % repr(self._transform))
+        
+
+# import here to avoid import cycle; needed for BaseTransform.__mul__.
+from .chain import ChainTransform
diff --git a/vispy/scene/transforms/chain.py b/vispy/scene/transforms/chain.py
new file mode 100644
index 0000000..bdfc306
--- /dev/null
+++ b/vispy/scene/transforms/chain.py
@@ -0,0 +1,234 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from ..shaders import FunctionChain
+from .base_transform import BaseTransform
+from .linear import NullTransform
+
+
+class ChainTransform(BaseTransform):
+    """
+    BaseTransform subclass that performs a sequence of transformations in
+    order. Internally, this class uses shaders.FunctionChain to generate
+    its glsl_map and glsl_imap functions.
+
+    Arguments:
+
+    transforms : list of BaseTransform instances
+    """
+    glsl_map = None
+    glsl_imap = None
+
+    Linear = False
+    Orthogonal = False
+    NonScaling = False
+    Isometric = False
+
+    def __init__(self, *transforms):
+        super(ChainTransform, self).__init__()
+
+        # Set input transforms
+        trs = []
+        for tr in transforms:
+            if isinstance(tr, (tuple, list)):
+                trs.extend(tr)
+            else:
+                trs.append(tr)
+        self._transforms = trs
+
+        # ChainTransform does not have shader maps
+        self._shader_map = None
+        self._shader_imap = None
+
+    @property
+    def transforms(self):
+        """ Get the list of transform that make up the transform chain.
+        """
+        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
+
+    @property
+    def Linear(self):
+        b = True
+        for tr in self._transforms:
+            b &= tr.Linear
+        return b
+
+    @property
+    def Orthogonal(self):
+        b = True
+        for tr in self._transforms:
+            b &= tr.Orthogonal
+        return b
+
+    @property
+    def NonScaling(self):
+        b = True
+        for tr in self._transforms:
+            b &= tr.NonScaling
+        return b
+
+    @property
+    def Isometric(self):
+        b = True
+        for tr in self._transforms:
+            b &= tr.Isometric
+        return b
+
+    def map(self, obj):
+        for tr in reversed(self.transforms):
+            obj = tr.map(obj)
+        return obj
+
+    def imap(self, obj):
+        for tr in self.transforms:
+            obj = tr.imap(obj)
+        return obj
+
+    def shader_map(self):
+        if self._shader_map is None:
+            self._shader_map = self._make_shader_map(imap=False)
+        else:
+            for tr in self._transforms:
+                tr.shader_map()  # force transform to update its shader
+        return self._shader_map
+
+    def shader_imap(self):
+        if self._shader_imap is None:
+            self._shader_imap = self._make_shader_map(imap=True)
+        else:
+            for tr in self._transforms:
+                tr.shader_imap()  # force transform to update its shader
+        return self._shader_imap
+
+    def _make_shader_map(self, imap):
+        if bool(imap):
+            funcs = [tr.shader_imap() for tr in self.transforms]
+        else:
+            funcs = [tr.shader_map() for tr in reversed(self.transforms)]
+
+        name = "transform_%s_chain" % ('imap' if bool(imap) else 'map')
+        return FunctionChain(name, funcs)
+
+    def flat(self):
+        """
+        Return a simplified chain by expanding any nested chains.
+        """
+        transforms = self.transforms[:]
+        new_chain = []
+        while len(transforms) > 0:
+            tr = transforms.pop(0)
+            if isinstance(tr, ChainTransform):
+                transforms = tr.transforms[:] + transforms
+            else:
+                new_chain.append(tr)
+        
+        return ChainTransform(new_chain)
+
+    def simplified(self):
+        """
+        Return a simplified chain by joining adjacent transforms.
+        If the result is a single transform, return that transform.
+        """
+        tr = self.flat()
+        if len(tr.transforms) == 0:
+            return NullTransform()
+        cont = True
+        tr = tr.transforms
+        while cont:
+            new_tr = [tr[0]]
+            cont = False
+            for t2 in tr[1:]:
+                t1 = new_tr[-1]
+                pr = t1 * t2
+                if not isinstance(pr, ChainTransform):
+                    cont = True
+                    new_tr.pop()
+                    new_tr.append(pr)
+                else:
+                    new_tr.append(t2)
+            tr = new_tr
+
+        if len(tr) == 1:
+            return tr[0]
+        else:
+            return ChainTransform(tr)
+
+    def append(self, tr):
+        """
+        Add a new transform to the end of this chain.
+        """
+        self.transforms.append(tr)
+        self.update()
+        # Keep simple for now. Let's look at efficienty later
+        # I feel that this class should not decide when to compose transforms
+#         while len(self.transforms) > 0:
+#             pr = tr * self.transforms[-1]
+#             if isinstance(pr, ChainTransform):
+#                 self.transforms.append(tr)
+#                 break
+#             else:
+#                 self.transforms.pop()
+#                 tr = pr
+#                 if len(self.transforms)  == 0:
+#                     self._transforms = [pr]
+#                     break
+
+    def prepend(self, tr):
+        """
+        Add a new transform to the beginning of this chain.
+        """
+        self.transforms.insert(0, tr)
+        self.update()
+        # Keep simple for now. Let's look at efficienty later
+#         while len(self.transforms) > 0:
+#             pr = self.transforms[0] * tr
+#             if isinstance(pr, ChainTransform):
+#                 self.transforms.insert(0, tr)
+#                 break
+#             else:
+#                 self.transforms.pop(0)
+#                 tr = pr
+#                 if len(self.transforms)  == 0:
+#                     self._transforms = [pr]
+#                     break
+
+    def __setitem__(self, index, tr):
+        self._transforms[index] = tr
+        if self._shader_map is not None:
+            self._shader_map[-(index+1)] = tr.shader_map()
+        if self._shader_imap is not None:
+            self._shader_imap[index] = tr.shader_imap()
+        self.update()
+
+    def __mul__(self, tr):
+        if isinstance(tr, ChainTransform):
+            trs = tr.transforms
+        else:
+            trs = [tr]
+        return ChainTransform(self.transforms+trs)
+
+    def __rmul__(self, tr):
+        if isinstance(tr, ChainTransform):
+            trs = tr.transforms
+        else:
+            trs = [tr]
+        return ChainTransform(trs+self.transforms)
+
+    def __str__(self):
+        names = [tr.__class__.__name__ for tr in self.transforms]
+        return "<ChainTransform [%s] at 0x%x>" % (", ".join(names), id(self))
+    
+    def __repr__(self):
+        tr = ",\n                 ".join(map(repr, self.transforms))
+        return "<ChainTransform [%s] at 0x%x>" % (tr, id(self))
diff --git a/vispy/scene/transforms/linear.py b/vispy/scene/transforms/linear.py
new file mode 100644
index 0000000..92f0623
--- /dev/null
+++ b/vispy/scene/transforms/linear.py
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from ...util import transforms
+from ...geometry import Rect
+from ._util import arg_to_vec4, as_vec4
+from .base_transform import BaseTransform
+
+
+class NullTransform(BaseTransform):
+    """ Transform having no effect on coordinates (identity transform).
+    """
+    glsl_map = "vec4 null_transform_map(vec4 pos) {return pos;}"
+    glsl_imap = "vec4 null_transform_imap(vec4 pos) {return pos;}"
+
+    Linear = True
+    Orthogonal = True
+    NonScaling = True
+    Isometric = True
+
+    def map(self, obj):
+        return obj
+
+    def imap(self, obj):
+        return obj
+
+    def __mul__(self, tr):
+        return tr
+
+    def __rmul__(self, tr):
+        return tr
+
+
+class STTransform(BaseTransform):
+    """ Transform performing only scale and translate, in that order.
+
+    Parameters
+    ----------
+    scale : array-like
+        Scale factors for X, Y, Z axes.
+    translate : array-like
+        Scale factors for X, Y, Z axes.
+    """
+    glsl_map = """
+        vec4 st_transform_map(vec4 pos) {
+            return (pos * $scale) + $translate;
+        }
+    """
+
+    glsl_imap = """
+        vec4 st_transform_imap(vec4 pos) {
+            return (pos - $translate) / $scale;
+        }
+    """
+
+    Linear = True
+    Orthogonal = True
+    NonScaling = False
+    Isometric = False
+
+    def __init__(self, scale=None, translate=None):
+        super(STTransform, self).__init__()
+
+        self._scale = np.ones(4, dtype=np.float32)
+        self._translate = np.zeros(4, 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
+
+    @arg_to_vec4
+    def map(self, coords):
+        n = coords.shape[-1]
+        return coords * self.scale[:n] + self.translate[:n]
+
+    @arg_to_vec4
+    def imap(self, coords):
+        n = coords.shape[-1]
+        return (coords - self.translate[:n]) / self.scale[:n]
+
+    def shader_map(self):
+        self._shader_map['scale'] = self.scale
+        self._shader_map['translate'] = self.translate
+        return self._shader_map
+
+    def shader_imap(self):
+        self._shader_imap['scale'] = self.scale
+        self._shader_imap['translate'] = self.translate
+        return self._shader_imap
+
+    @property
+    def scale(self):
+        return self._scale.copy()
+
+    @scale.setter
+    def scale(self, s, update=True):
+        if np.all(s == self._scale[:len(s)]):
+            return
+        self._scale[:len(s)] = s[:4]
+        self._scale[len(s):] = 1.0
+        if update:
+            self.shader_map()  # update shader variables
+            self.shader_imap()
+            self._update()
+
+    @property
+    def translate(self):
+        return self._translate.copy()
+
+    @translate.setter
+    def translate(self, t, update=True):
+        if np.all(t == self._translate[:len(t)]):
+            return
+        self._translate[:len(t)] = t[:4]
+        self._translate[len(t):] = 0.0
+        if update:
+            self.shader_map()  # update shader variables
+            self.shader_imap()
+            self._update()
+
+    def as_affine(self):
+        m = AffineTransform()
+        m.scale(self.scale)
+        m.translate(self.translate)
+        return m
+    
+    def _update(self):
+        # force update of uniforms on shader functions
+        self.shader_map()
+        self.shader_imap()
+        self.update()
+
+    @classmethod
+    def from_mapping(cls, x0, x1):
+        """ Create an STTransform from the given mapping. 
+        See ``set_mapping()`` for details.
+        """
+        t = cls()
+        t.set_mapping(x0, x1)
+        return t
+    
+    def set_mapping(self, x0, x1):
+        """ Configure this transform such that it maps points x0 => x1, 
+        where each argument must be an array of shape (2, 2) or (2, 3).
+        
+        For example, if we wish to map the corners of a rectangle::
+        
+            p1 = [[0, 0], [200, 300]]
+            
+        onto a unit cube::
+        
+            p2 = [[-1, -1], [1, 1]]
+            
+        then we can generate the transform as follows::
+        
+            tr = STTransform()
+            tr.set_mapping(p1, p2)
+            
+            # test:
+            assert tr.map(p1)[:,:2] == p2
+        
+        """
+        # if args are Rect, convert to array first
+        if isinstance(x0, Rect):
+            x0 = x0._transform_in()[:3]
+        if isinstance(x1, Rect):
+            x1 = x1._transform_in()[:3]
+        
+        x0 = np.array(x0)
+        x1 = np.array(x1)
+        denom = (x0[1] - x0[0])
+        mask = denom == 0
+        denom[mask] = 1.0 
+        s = (x1[1] - x1[0]) / denom
+        s[mask] = 1.0
+        s[x0[1] == x0[0]] = 1.0
+        t = x1[0] - s * x0[0]
+        
+        STTransform.scale.fset(self, s, update=False)
+        self.translate = t
+
+    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 super(STTransform, self).__mul__(tr)
+
+    def __rmul__(self, tr):
+        if isinstance(tr, AffineTransform):
+            return tr * self.as_affine()
+        return super(STTransform, self).__rmul__(tr)
+
+    def __repr__(self):
+        return ("<STTransform scale=%s translate=%s>"
+                % (self.scale, self.translate))
+
+
+class AffineTransform(BaseTransform):
+    """Affine transformation class
+
+    Parameters
+    ----------
+    matrix : array-like
+        4x4 array to use for the transform.
+    """
+    glsl_map = """
+        vec4 affine_transform_map(vec4 pos) {
+            return $matrix * pos;
+        }
+    """
+
+    glsl_imap = """
+        vec4 affine_transform_imap(vec4 pos) {
+            return $inv_matrix * pos;
+        }
+    """
+
+    Linear = True
+    Orthogonal = False
+    NonScaling = False
+    Isometric = False
+
+    def __init__(self, matrix=None):
+        super(AffineTransform, self).__init__()
+        if matrix is not None:
+            self.matrix = matrix
+        else:
+            self.reset()
+
+    @arg_to_vec4
+    def map(self, coords):
+        # looks backwards, but both matrices are transposed.
+        return np.dot(coords, self.matrix)
+
+    @arg_to_vec4
+    def imap(self, coords):
+        return np.dot(coords, self.inv_matrix)
+
+    def shader_map(self):
+        fn = super(AffineTransform, self).shader_map()
+        fn['matrix'] = self.matrix  # uniform mat4
+        return fn
+
+    def shader_imap(self):
+        fn = super(AffineTransform, self).shader_imap()
+        fn['inv_matrix'] = self.inv_matrix  # uniform mat4
+        return fn
+
+    @property
+    def matrix(self):
+        return self._matrix
+
+    @matrix.setter
+    def matrix(self, m):
+        self._matrix = m
+        self._inv_matrix = None
+        self.shader_map()
+        self.shader_imap()
+        self.update()
+
+    @property
+    def inv_matrix(self):
+        if self._inv_matrix is None:
+            self._inv_matrix = np.linalg.inv(self.matrix)
+        return self._inv_matrix
+
+    @arg_to_vec4
+    def translate(self, pos):
+        """
+        Translate the matrix by *pos*.
+
+        The translation is applied *after* the transformations already present
+        in the matrix.
+        """
+        self.matrix = transforms.translate(self.matrix, *pos[0, :3])
+
+    def scale(self, scale, center=None):
+        """
+        Scale the matrix by *scale* around the origin *center*.
+
+        The scaling is applied *after* the transformations already present
+        in the matrix.
+        """
+        scale = as_vec4(scale, default=(1, 1, 1, 1))
+        if center is not None:
+            center = as_vec4(center)[0, :3]
+            m = transforms.translate(self.matrix, *(-center))
+            m = transforms.scale(m, *scale[0, :3])
+            m = transforms.translate(self.matrix, *center)
+            self.matrix = m
+        else:
+            self.matrix = transforms.scale(self.matrix, *scale[0, :3])
+
+    def rotate(self, angle, axis):
+        #tr = transforms.rotate(np.eye(4), angle, *axis)
+        #self.matrix = np.dot(tr, self.matrix)
+        self.matrix = transforms.rotate(self.matrix, angle, *axis)
+
+    def set_mapping(self, points1, points2):
+        """ Set to a 3D transformation matrix that maps points1 onto points2.
+        
+        Arguments are specified as arrays of four 3D coordinates, shape (4, 3).
+        """
+        # note: need to transpose because util.functions uses opposite
+        # of standard linear algebra order.
+        self.matrix = transforms.affine_map(points1, points2).T
+
+    def set_ortho(self, l, r, b, t, n, f):
+        self.matrix = transforms.ortho(l, r, b, t, n, f)
+
+    def reset(self):
+        self.matrix = np.eye(4)
+
+    def __mul__(self, tr):
+        if (isinstance(tr, AffineTransform) and not 
+                any(tr.matrix[:3, 3] != 0)):   
+            # don't multiply if the perspective column is used
+            return AffineTransform(matrix=np.dot(tr.matrix, self.matrix))
+        else:
+            return tr.__rmul__(self)
+            #return super(AffineTransform, self).__mul__(tr)
+
+    def __repr__(self):
+        s = "%s(matrix=[" % self.__class__.__name__
+        indent = " "*len(s)
+        s += str(list(self.matrix[0])) + ",\n"
+        s += indent + str(list(self.matrix[1])) + ",\n"
+        s += indent + str(list(self.matrix[2])) + ",\n"
+        s += indent + str(list(self.matrix[3])) + "] at 0x%x)" % id(self)
+        return s
+
+
+#class SRTTransform(BaseTransform):
+#    """ Transform performing scale, rotate, and translate, in that order.
+#
+#    This transformation allows objects to be placed arbitrarily in a scene
+#    much the same way AffineTransform does. However, an incorrect order of
+#    operations in AffineTransform may result in shearing the object (if scale
+#    is applied after rotate) or in unpredictable translation (if scale/rotate
+#    is applied after translation). SRTTransform avoids these problems by
+#    enforcing the correct order of operations.
+#    """
+#    # TODO
+
+
+class PerspectiveTransform(AffineTransform):
+    """
+    Matrix transform that also implements perspective division.
+    
+    """
+    # Note: Although OpenGL operates in homogeneouus coordinates, it may be
+    # necessary to manually implement perspective division.. 
+    # Perhaps we can find a way to avoid this.
+    glsl_map = """
+        vec4 perspective_transform_map(vec4 pos) {
+            vec4 p = $matrix * pos;
+            p = p / p.w;
+            //p.z = 0;
+            p.w = 1;
+            return p;
+        }
+    """
+
+    ## Note 2: Are perspective matrices invertible??
+    #glsl_imap = """
+    #    vec4 perspective_transform_imap(vec4 pos) {
+    #        return $inv_matrix * pos;
+    #    }
+    #"""
+
+    # todo: merge with affinetransform?
+    def set_perspective(self, fov, aspect, near, far):
+        self.matrix = transforms.perspective(fov, aspect, near, far)
+
+    def set_frustum(self, l, r, b, t, n, f):
+        self.matrix = transforms.frustum(l, r, b, t, n, f)
+
+    @arg_to_vec4
+    def map(self, coords):
+        # looks backwards, but both matrices are transposed.
+        v = np.dot(coords, self.matrix)
+        v /= v[:, 3]
+        v[:, 2] = 0
+        return v
+
+    #@arg_to_vec4
+    #def imap(self, coords):
+    #    return np.dot(coords, self.inv_matrix)
+
+    def __mul__(self, tr):
+        # Override multiplication -- this does not combine well with affine
+        # matrices.
+        return tr.__rmul__(self)
diff --git a/vispy/scene/transforms/nonlinear.py b/vispy/scene/transforms/nonlinear.py
new file mode 100644
index 0000000..9e83b39
--- /dev/null
+++ b/vispy/scene/transforms/nonlinear.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from ._util import arg_to_array
+from .base_transform import BaseTransform
+
+
+class LogTransform(BaseTransform):
+    """ Transform perfoming logarithmic transformation on three axes.
+
+    Maps (x, y, z) => (log(base.x, x), log(base.y, y), log(base.z, z))
+
+    No transformation is applied for axes with base == 0.
+
+    If base < 0, then the inverse function is applied: x => base.x ** x
+
+    Parameters
+    ----------
+    base : array-like
+        Base for the X, Y, Z axes.
+    """
+
+    # TODO: Evaluate the performance costs of using conditionals.
+    # An alternative approach is to transpose the vector before
+    # log-transforming, and then transpose back afterward.
+    glsl_map = """
+        vec4 LogTransform_map(vec4 pos) {
+            if($base.x > 1.0)
+                pos.x = log(pos.x) / log($base.x);
+            else if($base.x < -1.0)
+                pos.x = pow(-$base.x, pos.x);
+
+            if($base.y > 1.0)
+                pos.y = log(pos.y) / log($base.y);
+            else if($base.y < -1.0)
+                pos.y = pow(-$base.y, pos.y);
+
+            if($base.z > 1.0)
+                pos.z = log(pos.z) / log($base.z);
+            else if($base.z < -1.0)
+                pos.z = pow(-$base.z, pos.z);
+            return pos;
+        }
+        """
+
+    glsl_imap = glsl_map
+
+    Linear = False
+    Orthogonal = True
+    NonScaling = False
+    Isometric = False
+
+    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
+
+    @property
+    def base(self):
+        """
+        *base* is a tuple (x, y, z) containing the log base that should be
+        applied to each axis of the input vector. If any axis has a base <= 0,
+        then that axis is not affected.
+        """
+        return self._base.copy()
+
+    @base.setter
+    def base(self, s):
+        self._base[:len(s)] = s
+        self._base[len(s):] = 0.0
+
+    @arg_to_array
+    def map(self, coords, base=None):
+        ret = np.empty(coords.shape, coords.dtype)
+        if base is None:
+            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])
+            elif base[i] < -1.0:
+                ret[..., i] = -base[i] ** coords[..., i]
+            else:
+                ret[..., i] = coords[..., i]
+        return ret
+
+    @arg_to_array
+    def imap(self, coords):
+        return self.map(coords, -self.base)
+
+    def shader_map(self):
+        fn = super(LogTransform, self).shader_map()
+        fn['base'] = self.base  # uniform vec3
+        return fn
+
+    def shader_imap(self):
+        fn = super(LogTransform, self).shader_imap()
+        fn['base'] = -self.base  # uniform vec3
+        return fn
+
+    def __repr__(self):
+        return "<LogTransform base=%s>" % (self.base)
+
+
+class PolarTransform(BaseTransform):
+    """Polar transform
+
+    Maps (theta, r, z) to (x, y, z), where `x = r*cos(theta)`
+    and `y = r*sin(theta)`.
+    """
+    glsl_map = """
+        vec4 polar_transform_map(vec4 pos) {
+            return vec4(pos.y * cos(pos.x), pos.y * sin(pos.x), pos.z, 1);
+        }
+        """
+
+    glsl_imap = """
+        vec4 polar_transform_map(vec4 pos) {
+            // TODO: need some modulo math to handle larger theta values..?
+            float theta = atan(pos.y, pos.x);
+            float r = length(pos.xy);
+            return vec4(theta, r, pos.z, 1);
+        }
+        """
+
+    Linear = False
+    Orthogonal = False
+    NonScaling = False
+    Isometric = False
+
+    @arg_to_array
+    def map(self, coords):
+        ret = np.empty(coords.shape, coords.dtype)
+        ret[..., 0] = coords[..., 1] * np.cos(coords[..., 0])
+        ret[..., 1] = coords[..., 1] * np.sin(coords[..., 0])
+        for i in range(2, coords.shape[-1]):  # copy any further axes
+            ret[..., i] = coords[..., i]
+        return ret
+
+    @arg_to_array
+    def imap(self, coords):
+        ret = np.empty(coords.shape, coords.dtype)
+        ret[..., 0] = np.arctan2(coords[..., 0], coords[..., 1])
+        ret[..., 1] = (coords[..., 0]**2 + coords[..., 1]**2) ** 0.5
+        for i in range(2, coords.shape[-1]):  # copy any further axes
+            ret[..., i] = coords[..., i]
+        return ret
+
+
+#class BilinearTransform(BaseTransform):
+#    # TODO
+#    pass
+
+
+#class WarpTransform(BaseTransform):
+#    """ Multiple bilinear transforms in a grid arrangement.
+#    """
+#    # TODO
diff --git a/vispy/scene/transforms/tests/test_transforms.py b/vispy/scene/transforms/tests/test_transforms.py
new file mode 100644
index 0000000..4c2629a
--- /dev/null
+++ b/vispy/scene/transforms/tests/test_transforms.py
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+
+import vispy.scene.transforms as tr
+from vispy.geometry import Rect
+
+NT = tr.NullTransform
+ST = tr.STTransform
+AT = tr.AffineTransform
+PT = tr.PolarTransform
+LT = tr.LogTransform
+CT = tr.ChainTransform
+
+
+def assert_chain_types(chain, types):
+    assert list(map(type, chain.transforms)) == types
+
+
+def assert_chain_objects(chain1, chain2):
+    assert chain1.transforms == chain2.transforms
+
+
+def tesst_multiplication():
+    n = NT()
+    s = ST()
+    a = AT()
+    p = PT()
+    l = LT()
+    c1 = CT([s, a, p])
+    assert c1
+    c2 = CT([s, a, s])
+
+    assert isinstance(n * n, NT)
+    assert isinstance(n * s, ST)
+    assert isinstance(s * s, ST)
+    assert isinstance(a * s, AT)
+    assert isinstance(a * a, AT)
+    assert isinstance(s * a, AT)
+    assert isinstance(n * p, PT)
+    assert isinstance(s * p, CT)
+    assert_chain_types(s * p, [PT, ST])
+    assert_chain_types(s * p * a, [ST, PT, AT])
+    assert_chain_types(s * a * p, [PT, AT])
+    assert_chain_types(s * p * s, [ST, PT, ST])
+    assert_chain_types(s * a * p * s * a, [AT, PT, AT])
+    assert_chain_types(c2 * a, [AT])
+    assert_chain_types(p * l * s, [ST, LT, PT])
+
+
+def test_transform_chain():
+    # Make dummy classes for easier distinguishing the transforms
+
+    class DummyTrans(tr.BaseTransform):
+        glsl_map = "vec4 trans(vec4 pos) {return pos;}"
+        glsl_imap = "vec4 trans(vec4 pos) {return pos;}"
+
+    class TransA(DummyTrans):
+        pass
+
+    class TransB(DummyTrans):
+        pass
+
+    class TransC(DummyTrans):
+        pass
+
+    # Create test transforms
+    a, b, c = TransA(), TransB(), TransC()
+
+    # Test Chain creation
+    assert tr.ChainTransform().transforms == []
+    assert tr.ChainTransform(a).transforms == [a]
+    assert tr.ChainTransform(a, b).transforms == [a, b]
+    assert tr.ChainTransform(a, b, c, a).transforms == [a, b, c, a]
+
+    # Test composition by multiplication
+    assert_chain_objects(a * b, tr.ChainTransform(a, b))
+    assert_chain_objects(a * b * c, tr.ChainTransform(a, b, c))
+    assert_chain_objects(a * b * c * a, tr.ChainTransform(a, b, c, a))
+
+    # Test adding/prepending to transform
+    chain = tr.ChainTransform()
+    chain.append(a)
+    assert chain.transforms == [a]
+    chain.append(b)
+    assert chain.transforms == [a, b]
+    chain.append(c)
+    assert chain.transforms == [a, b, c]
+    chain.prepend(b)
+    assert chain.transforms == [b, a, b, c]
+    chain.prepend(c)
+    assert chain.transforms == [c, b, a, b, c]
+
+    # Test flattening
+    chain1 = tr.ChainTransform(a, b)
+    chain2 = tr.ChainTransform(c)
+    chain3 = tr.ChainTransform(chain1, chain2)
+    chain4 = tr.ChainTransform(a, b, c, chain3)
+    chain5 = chain4.flat()
+    assert chain5.transforms == [a, b, c, a, b, c]
+
+    # Test simplifying
+    t1 = tr.STTransform(scale=(2, 3))
+    t2 = tr.STTransform(translate=(3, 4))
+    t3 = tr.STTransform(translate=(3, 4))
+    # Create multiplied versions
+    t123 = t1*t2*t3
+    t321 = t3*t2*t1
+    c123 = tr.ChainTransform(t1, t2, t3)
+    c321 = tr.ChainTransform(t3, t2, t1)
+    c123s = c123.simplified()
+    c321s = c321.simplified()
+    #
+    assert isinstance(t123, tr.STTransform)  # or the test is useless
+    assert isinstance(t321, tr.STTransform)  # or the test is useless
+    assert isinstance(c123s, tr.STTransform)  # or the test is useless
+    assert isinstance(c321s, tr.STTransform)  # or the test is useless
+    assert np.all(c123s.scale == t123.scale)
+    assert np.all(c123s.translate == t123.translate)
+    assert np.all(c321s.scale == t321.scale)
+    assert np.all(c321s.translate == t321.translate)
+
+    # Test Mapping
+    t1 = tr.STTransform(scale=(2, 3))
+    t2 = tr.STTransform(translate=(3, 4))
+    chain1 = tr.ChainTransform(t1, t2)
+    chain2 = tr.ChainTransform(t2, t1)
+    #
+    assert chain1.transforms == [t1, t2]  # or the test is useless
+    assert chain2.transforms == [t2, t1]  # or the test is useless
+    #
+    m12 = (t1*t2).map((1, 1)).tolist()
+    m21 = (t2*t1).map((1, 1)).tolist()
+    m12_ = chain1.map((1, 1)).tolist()
+    m21_ = chain2.map((1, 1)).tolist()
+    #
+    #print(m12, m21, m12_, m21_)
+    assert m12 != m21
+    assert m12 == m12_
+    assert m21 == m21_
+
+    # Test shader map
+    t1 = tr.STTransform(scale=(2, 3))
+    t2 = tr.STTransform(translate=(3, 4))
+    chain = tr.ChainTransform(t1, t2)
+    #
+    funcs = chain.shader_map().dependencies()
+    funcsi = chain.shader_imap().dependencies()
+    #
+    assert t1.shader_map() in funcs
+    assert t2.shader_map() in funcs
+    assert t1.shader_imap() in funcsi
+    assert t2.shader_imap() in funcsi
+
+
+def test_map_rect():
+    r = Rect((2, 7), (13, 19))
+    r1 = ST(scale=(2, 2), translate=(-10, 10)).map(r)
+    assert r1 == Rect((-6, 24), (26, 38))
+
+
+def test_st_mapping():
+    p1 = [[5., 7.], [23., 8.]]
+    p2 = [[-1.3, -1.4], [1.1, 1.2]]
+
+    t = tr.STTransform()
+    t.set_mapping(p1, p2)
+
+    assert np.allclose(t.map(p1)[:, :len(p2)], p2)
+
+
+def test_affine_mapping():
+    t = tr.AffineTransform()
+    p1 = np.array([[0, 0, 0],
+                   [1, 0, 0],
+                   [0, 1, 0],
+                   [0, 0, 1]])
+
+    # test pure translation
+    p2 = p1 + 5.5
+    t.set_mapping(p1, p2)
+    assert np.allclose(t.map(p1)[:, :p2.shape[1]], p2)
+
+    # test pure scaling
+    p2 = p1 * 5.5
+    t.set_mapping(p1, p2)
+    assert np.allclose(t.map(p1)[:, :p2.shape[1]], p2)
+
+    # test scale + translate
+    p2 = (p1 * 5.5) + 3.5
+    t.set_mapping(p1, p2)
+    assert np.allclose(t.map(p1)[:, :p2.shape[1]], p2)
+
+    # test SRT
+    p2 = np.array([[10, 5, 3],
+                   [10, 15, 3],
+                   [30, 5, 3],
+                   [10, 5, 3.5]])
+    t.set_mapping(p1, p2)
+    assert np.allclose(t.map(p1)[:, :p2.shape[1]], p2)
+
+
+def test_inverse():
+    m = np.random.normal(size=(4, 4))
+    transforms = [
+        NT(),
+        ST(scale=(1e-4, 2e5), translate=(10, -6e9)),
+        AT(m),
+    ]
+
+    np.random.seed(0)
+    N = 20
+    x = np.random.normal(size=(N, 3))
+    pw = np.random.normal(size=(N, 3), scale=3)
+    pos = x * 10 ** pw
+
+    for trn in transforms:
+        assert np.allclose(pos, trn.inverse.map(trn.map(pos))[:, :3])
+
+    # log transform only works on positive values
+    #abs_pos = np.abs(pos)
+    #tr = LT(base=(2, 4.5, 0))
+    #assert np.allclose(abs_pos, tr.inverse.map(tr.map(abs_pos))[:,:3])
+
+
+if __name__ == '__main__':
+    for key in [key for key in globals()]:
+        if key.startswith('test_'):
+            func = globals()[key]
+            print('running', func.__name__)
+            func()
diff --git a/vispy/scene/visuals/__init__.py b/vispy/scene/visuals/__init__.py
new file mode 100644
index 0000000..d89502d
--- /dev/null
+++ b/vispy/scene/visuals/__init__.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+The vispy.scene.visuals namespace provides a wide range of visuals.
+A Visual is an Entity that displays something.
+
+Visuals do not have to be used in a scenegraph per se; they can also
+be used stand-alone e.g. from a vispy.app.Canvas, or using Glut.
+
+This module provides a library of drawable objects that are intended to
+encapsulate simple graphic objects such as lines, meshes, points, 2D shapes,
+images, text, etc.
+"""
+
+__all__ = ['Visual', 'Ellipse', 'GridLines', 'Image', 'Line', 'LinePlot',
+           'Markers', 'marker_types', 'Mesh', 'Polygon', 'Rectangle',
+           'RegularPolygon', 'SurfacePlot', 'Text', 'XYZAxis']
+
+from .visual import Visual  # noqa
+from .line import Line  # noqa
+from .markers import Markers, marker_types  # noqa
+from .mesh import Mesh  # noqa
+from .image import Image  # noqa
+from .polygon import Polygon  # noqa
+from .ellipse import Ellipse  # noqa
+from .regular_polygon import RegularPolygon  # noqa
+from .rectangle import Rectangle  # noqa
+from .text import Text  # noqa
+from .gridlines import GridLines  # noqa
+from .surface_plot import SurfacePlot  # noqa
+from .isosurface import Isosurface  # noqa
+from .isocurve import Isocurve  # noqa
+from .xyz_axis import XYZAxis  # noqa
+from .line_plot import LinePlot  # noqa
diff --git a/vispy/scene/visuals/ellipse.py b/vispy/scene/visuals/ellipse.py
new file mode 100644
index 0000000..a589e9f
--- /dev/null
+++ b/vispy/scene/visuals/ellipse.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+
+"""
+Simple ellipse visual based on PolygonVisual
+"""
+
+from __future__ import division
+
+import numpy as np
+from ...color import Color
+from .polygon import Polygon, Mesh, Line
+
+
+class Ellipse(Polygon):
+    """
+    Displays a 2D ellipse
+
+    Parameters
+    ----------
+    pos : array
+        Center of the ellipse
+    radius : float | tuple
+        Radius or radii of the ellipse
+        Defaults to  (0.1, 0.1)
+    start_angle : float
+        Start angle of the ellipse in degrees
+        Defaults to 0.
+    span_angle : float
+        Span angle of the ellipse in degrees
+        Defaults to 0.
+    num_segments : int
+        Number of segments to be used to draw the ellipse
+        Defaults to 100
+    """
+    def __init__(self, pos=None, color='black', border_color=None,
+                 radius=(0.1, 0.1), start_angle=0., span_angle=360.,
+                 num_segments=100, **kwds):
+        super(Ellipse, self).__init__()
+        self._vertices = None
+        self._pos = pos
+        self._color = Color(color)
+        self._border_color = Color(border_color)
+        self._radius = radius
+        self._start_angle = start_angle
+        self._span_angle = span_angle
+        self._num_segments = num_segments
+        self._update()
+
+    def _generate_vertices(self, pos, radius, start_angle, span_angle,
+                           num_segments):
+        if isinstance(radius, (list, tuple)):
+            if len(radius) == 2:
+                xr, yr = radius
+            else:
+                raise ValueError("radius must be float or 2 value tuple/list"
+                                 " (got %s of length %d)" % (type(radius),
+                                                             len(radius)))
+        else:
+            xr = yr = radius
+        curve_segments = int(num_segments * span_angle / 360.)
+        start_angle *= (np.pi/180.)
+        self._vertices = np.empty([curve_segments+2, 2], dtype=np.float32)
+        self._vertices[0] = np.float32([pos[0], pos[1]])
+        theta = np.linspace(start_angle, start_angle + (span_angle/180.)*np.pi,
+                            curve_segments+1)
+        self._vertices[1:, 0] = pos[0] + xr * np.cos(theta)
+        self._vertices[1:, 1] = pos[1] + yr * np.sin(theta)
+
+    @property
+    def radius(self):
+        """ The start radii of the ellipse.
+        """
+        return self._radius
+
+    @radius.setter
+    def radius(self, radius):
+        self._radius = radius
+        self._update()
+
+    @property
+    def start_angle(self):
+        """ The start start_angle of the ellipse.
+        """
+        return self._start_angle
+
+    @start_angle.setter
+    def start_angle(self, start_angle):
+        self._start_angle = start_angle
+        self._update()
+
+    @property
+    def span_angle(self):
+        """ The angular span of the ellipse.
+        """
+        return self._span_angle
+
+    @span_angle.setter
+    def span_angle(self, span_angle):
+        self._span_angle = span_angle
+        self._update()
+
+    @property
+    def num_segments(self):
+        """ The number of segments in the ellipse.
+        """
+        return self._num_segments
+
+    @num_segments.setter
+    def num_segments(self, num_segments):
+        if num_segments < 1:
+            raise ValueError('Ellipse must consist of more than 1 segment')
+        self._num_segments = num_segments
+        self._update()
+
+    def _update(self):
+        if self._pos is not None:
+            self._generate_vertices(pos=self._pos, radius=self._radius,
+                                    start_angle=self._start_angle,
+                                    span_angle=self._span_angle,
+                                    num_segments=self._num_segments)
+            self.mesh = Mesh(vertices=self._vertices, color=self._color.rgba,
+                             mode='triangle_fan')
+            if not self._border_color.is_blank():
+                self.border = Line(pos=self._vertices[1:],
+                                   color=self._border_color.rgba)
+        #self.update()
diff --git a/vispy/scene/visuals/gridlines.py b/vispy/scene/visuals/gridlines.py
new file mode 100644
index 0000000..e437ab0
--- /dev/null
+++ b/vispy/scene/visuals/gridlines.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from ... import gloo
+from .visual import Visual
+from ..shaders import ModularProgram
+
+
+VERT = """
+attribute vec2 pos;
+varying vec4 v_pos;
+void main() {
+    v_pos = vec4(pos, 0, 1);
+    gl_Position = v_pos;
+}
+"""
+
+FRAG = """
+varying vec4 v_pos;
+uniform vec2 scale;
+
+void main() {
+    vec4 px_pos = $map_nd_to_doc(v_pos);
+
+    // Compute vectors representing width, height of pixel in local coords
+    float s = 1.;
+    vec4 local_pos = $map_doc_to_local(px_pos);
+    vec4 dx = $map_doc_to_local(px_pos + vec4(1.0 / s, 0, 0, 0)) - local_pos;
+    vec4 dy = $map_doc_to_local(px_pos + vec4(0, 1.0 / s, 0, 0)) - local_pos;
+
+    // Pixel length along each axis, rounded to the nearest power of 10
+    vec2 px = s * vec2(abs(dx.x) + abs(dy.x), abs(dx.y) + abs(dy.y));
+    float log10 = log(10.0);
+    float sx = pow(10.0, floor(log(px.x) / log10)+1) * scale.x;
+    float sy = pow(10.0, floor(log(px.y) / log10)+1) * scale.y;
+
+    float max_alpha = 0.6;
+    float x_alpha = 0.0;
+    
+    if (mod(local_pos.x, 1000 * sx) < px.x) {
+        x_alpha = clamp(1 * sx/px.x, 0, max_alpha);
+    }
+    else if (mod(local_pos.x, 100 * sx) < px.x) {
+        x_alpha = clamp(.1 * sx/px.x, 0, max_alpha);
+    }
+    else if (mod(local_pos.x, 10 * sx) < px.x) {
+        x_alpha = clamp(0.01 * sx/px.x, 0, max_alpha);
+    }
+
+    float y_alpha = 0.0;
+    if (mod(local_pos.y, 1000 * sy) < px.y) {
+        y_alpha = clamp(1 * sy/px.y, 0, max_alpha);
+    }
+    else if (mod(local_pos.y, 100 * sy) < px.y) {
+        y_alpha = clamp(.1 * sy/px.y, 0, max_alpha);
+    }
+    else if (mod(local_pos.y, 10 * sy) < px.y) {
+        y_alpha = clamp(0.01 * sy/px.y, 0, max_alpha);
+    }
+
+    float alpha = (((log(max(x_alpha, y_alpha))/log(10.))+2) / 3);
+    if (alpha == 0) {
+        discard;
+    }
+    gl_FragColor = vec4(1, 1, 1, alpha);
+}
+"""
+
+
+class GridLines(Visual):
+    """
+    """
+    def __init__(self, scale=(1, 1), **kwds):
+        super(Visual, self).__init__(**kwds)
+        self._program = ModularProgram(VERT, FRAG)
+        self._vbo = None
+        self._scale = scale
+
+    def _buffer(self):
+        if self._vbo is None:
+            # quad covers entire view; frag. shader will deal with image shape
+            quad = np.array([[-1, -1, 0], [1, -1, 0], [1, 1, 0],
+                             [-1, -1, 0], [1, 1, 0], [-1, 1, 0]],
+                            dtype=np.float32)
+            self._vbo = gloo.VertexBuffer(quad)
+        return self._vbo
+
+    def draw(self, event):
+        gloo.set_state('additive', cull_face='front_and_back')
+
+        doc_to_ndc = event.entity_transform(map_from=event.document_cs,
+                                            map_to=event.render_cs)
+        local_to_doc = event.document_transform()
+
+        self._program.frag['map_nd_to_doc'] = doc_to_ndc.shader_imap()
+        self._program.frag['map_doc_to_local'] = local_to_doc.shader_imap()
+        self._program.prepare()
+        self._program['pos'] = self._buffer()
+        self._program['scale'] = self._scale
+        self._program.draw('triangles')
diff --git a/vispy/scene/visuals/image.py b/vispy/scene/visuals/image.py
new file mode 100644
index 0000000..13f905b
--- /dev/null
+++ b/vispy/scene/visuals/image.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from ... import gloo
+from ..transforms import STTransform, NullTransform
+from .modular_mesh import ModularMesh
+from ..components import (TextureComponent, VertexTextureCoordinateComponent,
+                          TextureCoordinateComponent)
+
+
+class Image(ModularMesh):
+    """Visual subclass displaying an image.
+
+    Parameters
+    ----------
+    data : (height, width, 4) ubyte array
+        Image data.
+    method : str
+        Selects method of rendering image in case of non-linear transforms.
+        Each method produces similar results, but may trade efficiency
+        and accuracy. If the transform is linear, this parameter is ignored
+        and a single quad is drawn around the area of the image.
+
+            * 'subdivide': Image is represented as a grid of triangles with
+              texture coordinates linearly mapped.
+            * 'impostor': Image is represented as a quad covering the entire
+              view, with texture coordinates determined by the transform.
+              This produces the best transformation results, but may be slow.
+
+    grid: tuple (rows, cols)
+        If method='subdivide', this tuple determines the number of rows and
+        columns in the image grid.
+    """
+    def __init__(self, data, method='subdivide', grid=(10, 10), **kwargs):
+        super(Image, self).__init__(**kwargs)
+
+        self._data = None
+
+        # maps from quad coordinates to texture coordinates
+        self._tex_transform = STTransform()
+
+        self._texture = None
+        self._interpolation = 'nearest'
+        self.set_data(data)
+        self.set_gl_options(cull_face=('front_and_back',))
+
+        self.method = method
+        self.grid = grid
+
+    def set_data(self, image=None, **kwds):
+        if image is not None:
+            self._data = image
+            self._texture = None
+        super(Image, self).set_data(**kwds)
+
+    @property
+    def interpolation(self):
+        return self._interpolation
+
+    @interpolation.setter
+    def interpolation(self, interp):
+        self._interpolation = interp
+        self.update()
+
+    @property
+    def size(self):
+        return self._data.shape[:2][::-1]
+
+    def _build_data(self, event):
+        # Construct complete data array with position and optionally color
+        if self.transform.Linear:
+            method = 'subdivide'
+            grid = (1, 1)
+        else:
+            method = self.method
+            grid = self.grid
+
+        # TODO: subdivision and impostor modes should be handled by new
+        # components?
+        if method == 'subdivide':
+            # quads cover area of image as closely as possible
+            w = 1.0 / grid[1]
+            h = 1.0 / grid[0]
+
+            quad = np.array([[0, 0, 0], [w, 0, 0], [w, h, 0],
+                             [0, 0, 0], [w, h, 0], [0, h, 0]],
+                            dtype=np.float32)
+            quads = np.empty((grid[1], grid[0], 6, 3), dtype=np.float32)
+            quads[:] = quad
+
+            mgrid = np.mgrid[0.:grid[1], 0.:grid[0]].transpose(1, 2, 0)
+            mgrid = mgrid[:, :, np.newaxis, :]
+            mgrid[..., 0] *= w
+            mgrid[..., 1] *= h
+
+            quads[..., :2] += mgrid
+            tex_coords = quads.reshape(grid[1]*grid[0]*6, 3)
+            vertices = tex_coords.copy()
+            vertices[..., 0] *= self._data.shape[1]
+            vertices[..., 1] *= self._data.shape[0]
+            ModularMesh.set_data(self, pos=vertices)
+            coords = np.ascontiguousarray(tex_coords[:, :2])
+            tex_coord_comp = TextureCoordinateComponent(coords)
+        elif method == 'impostor':
+            # quad covers entire view; frag. shader will deal with image shape
+            quad = np.array([[-1, -1, 0], [1, -1, 0], [1, 1, 0],
+                             [-1, -1, 0], [1, 1, 0], [-1, 1, 0]],
+                            dtype=np.float32)
+            ModularMesh.set_data(self, pos=quad)
+
+            self._tex_transform.scale = (1./self._data.shape[0],
+                                         1./self._data.shape[1])
+            ctr = event.render_transform.inverse
+            total_transform = self._tex_transform * ctr
+            tex_coord_comp = VertexTextureCoordinateComponent(total_transform)
+            tr = NullTransform().shader_map()
+            self._program.vert['map_local_to_nd'] = tr
+        else:
+            raise ValueError("Unknown image draw method '%s'" % method)
+
+        self._texture = gloo.Texture2D(self._data)
+        self._texture.interpolation = self._interpolation
+
+        self.color_components = [TextureComponent(self._texture,
+                                                  tex_coord_comp)]
+
+    def _activate_transform(self, event=None):
+        # this is handled in _build_data instead.
+        pass
+
+    def bounds(self, mode, axis):
+        if axis > 1:
+            return (0, 0)
+        else:
+            return (0, self.size[axis])
+
+    def draw(self, event):
+        if self._data is None:
+            return
+
+        if self.transform.Linear:
+            method = 'subdivide'
+        else:
+            method = self.method
+
+        # always have to rebuild for impostor, only first for subdivide
+        if self._texture is None or method == 'impostor':
+            self._build_data(event)
+        if method == 'subdivide':
+            tr = event.render_transform.shader_map()
+            self._program.vert['map_local_to_nd'] = tr
+
+        super(Image, self).draw(event)
diff --git a/vispy/scene/visuals/isocurve.py b/vispy/scene/visuals/isocurve.py
new file mode 100644
index 0000000..5791aa5
--- /dev/null
+++ b/vispy/scene/visuals/isocurve.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from .line import Line
+from ...geometry.isocurve import isocurve
+
+
+class Isocurve(Line):
+    """Displays an isocurve of a 2D scalar array.
+
+    Parameters
+    ----------
+    data : ndarray | None
+        2D scalar array.
+    level: float | None
+        The level at which the isocurve is constructed from *data*.
+
+    Notes
+    -----
+    """
+    def __init__(self, data=None, level=None, **kwds):
+        self._data = None
+        self._level = level
+        self._recompute = True
+        kwds['mode'] = 'gl'
+        kwds['antialias'] = False
+        Line.__init__(self, **kwds)
+        if data is not None:
+            self.set_data(data)
+
+    @property
+    def level(self):
+        """ The threshold at which the isocurve is constructed from the 
+        2D data.
+        """
+        return self._level
+    
+    @level.setter
+    def level(self, level):
+        self._level = level
+        self._recompute = True
+        self.update()
+
+    def set_data(self, data):
+        """ Set the scalar array data
+
+        Parameters:
+        -----------
+        data : ndarray
+            A 2D array of scalar values. The isocurve is constructed to show
+            all locations in the scalar field equal to ``self.level``.
+        """
+        self._data = data
+        self._recompute = True
+        self.update()
+
+    def draw(self, event):
+        if self._data is None or self._level is None:
+            return
+        
+        if self._recompute:
+            verts = []
+            paths = isocurve(self._data.astype(float).T, self._level, 
+                             extend_to_edge=True, connected=True)
+            tot = 0
+            gaps = []
+            for path in paths:
+                verts.extend(path)
+                tot += len(path)
+                gaps.append(tot-1)
+                
+            connect = np.ones(tot-1, dtype=bool)
+            connect[gaps[:-1]] = False
+            
+            verts = np.array(verts)
+            Line.set_data(self, pos=verts, connect=connect)
+            self._recompute = False
+            
+        Line.draw(self, event)
diff --git a/vispy/scene/visuals/isosurface.py b/vispy/scene/visuals/isosurface.py
new file mode 100644
index 0000000..d8e4b03
--- /dev/null
+++ b/vispy/scene/visuals/isosurface.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from .mesh import Mesh
+from ...geometry.isosurface import isosurface
+
+
+class Isosurface(Mesh):
+    """Displays an isosurface of a 3D scalar array.
+
+    Parameters
+    ----------
+    data : ndarray | None
+        3D scalar array.
+    level: float | None
+        The level at which the isosurface is constructed from *data*.
+
+    Notes
+    -----
+    """
+    def __init__(self, data=None, level=None, **kwds):
+        self._data = None
+        self._level = level
+        self._recompute = True
+        Mesh.__init__(self, **kwds)
+        if data is not None:
+            self.set_data(data)
+
+    @property
+    def level(self):
+        """ The threshold at which the isosurface is constructed from the 
+        3D data.
+        """
+        return self._level
+    
+    @level.setter
+    def level(self, level):
+        self._level = level
+        self._recompute = True
+        self.update()
+
+    def set_data(self, data):
+        """ Set the scalar array data
+
+        Parameters:
+        -----------
+        data : ndarray
+            A 3D array of scalar values. The isosurface is constructed to show
+            all locations in the scalar field equal to ``self.level``.
+        """
+        self._data = data
+        self._recompute = True
+        self.update()
+
+    def draw(self, event):
+        if self._data is None or self._level is None:
+            return
+        
+        if self._recompute:
+            verts, faces = isosurface(self._data, self._level)
+            Mesh.set_data(self, vertices=verts, faces=faces)
+            self._recompute = False
+            
+        Mesh.draw(self, event)
diff --git a/vispy/scene/visuals/line/__init__.py b/vispy/scene/visuals/line/__init__.py
new file mode 100644
index 0000000..b4cef19
--- /dev/null
+++ b/vispy/scene/visuals/line/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from .line import Line  # noqa
diff --git a/vispy/scene/visuals/line/dash_atlas.py b/vispy/scene/visuals/line/dash_atlas.py
new file mode 100644
index 0000000..f30e1e8
--- /dev/null
+++ b/vispy/scene/visuals/line/dash_atlas.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+
+
+class DashAtlas(object):
+
+    """  """
+
+    def __init__(self, shape=(64, 1024, 4)):
+        # 512 patterns at max
+        self._data = np.zeros(shape, dtype=np.float32)
+        self._index = 0
+        self._atlas = {}
+
+        self['solid'] = (1e20, 0), (1, 1)
+        self['densely dotted'] = (0, 1), (1, 1)
+        self['dotted'] = (0, 2), (1, 1)
+        self['loosely dotted'] = (0, 3), (1, 1)
+        self['densely dashed'] = (1, 1), (1, 1)
+        self['dashed'] = (1, 2), (1, 1)
+        self['loosely dashed'] = (1, 4), (1, 1)
+        self['densely dashdotted'] = (1, 1, 0, 1), (1, 1, 1, 1)
+        self['dashdotted'] = (1, 2, 0, 2), (1, 1, 1, 1)
+        self['loosely dashdotted'] = (1, 3, 0, 3), (1, 1, 1, 1)
+        self['densely dashdotdotted'] = (1, 1, 0, 1, 0, 1), (1, 1, 1, 1)
+        self['dashdotdotted'] = (1, 2, 0, 2, 0, 2), (1, 1, 1, 1, 1, 1)
+        self['loosely dashdotdotted'] = (1, 3, 0, 3, 0, 3), (1, 1, 1, 1)
+
+        self._dirty = True
+
+    def __getitem__(self, key):
+        return self._atlas[key]
+
+    def __setitem__(self, key, value):
+        data, period = self.make_pattern(value[0], value[1])
+        self._data[self._index] = data
+        self._atlas[key] = [self._index / float(self._data.shape[0]), period]
+        self._index += 1
+        self._dirty = True
+        # self.add_pattern(value)
+
+    def make_pattern(self, pattern, caps=[1, 1]):
+        """ """
+
+        # A pattern is defined as on/off sequence of segments
+        # It must be a multiple of 2
+        if len(pattern) > 1 and len(pattern) % 2:
+            pattern = [pattern[0] + pattern[-1]] + pattern[1:-1]
+        P = np.array(pattern)
+
+        # Period is the sum of all segment length
+        period = np.cumsum(P)[-1]
+
+        # Find all start and end of on-segment only
+        C, c = [], 0
+        for i in range(0, len(P) + 2, 2):
+            a = max(0.0001, P[i % len(P)])
+            b = max(0.0001, P[(i + 1) % len(P)])
+            C.extend([c, c + a])
+            c += a + b
+        C = np.array(C)
+
+        # Build pattern
+        length = self._data.shape[1]
+        Z = np.zeros((length, 4), dtype=np.float32)
+        for i in np.arange(0, len(Z)):
+            x = period * (i) / float(len(Z) - 1)
+            index = np.argmin(abs(C - (x)))
+            if index % 2 == 0:
+                if x <= C[index]:
+                    dash_type = +1
+                else:
+                    dash_type = 0
+                dash_start, dash_end = C[index], C[index + 1]
+            else:
+                if x > C[index]:
+                    dash_type = -1
+                else:
+                    dash_type = 0
+                dash_start, dash_end = C[index - 1], C[index]
+            Z[i] = C[index], dash_type, dash_start, dash_end
+        return Z, period
diff --git a/vispy/scene/visuals/line/fragment.py b/vispy/scene/visuals/line/fragment.py
new file mode 100644
index 0000000..b354173
--- /dev/null
+++ b/vispy/scene/visuals/line/fragment.py
@@ -0,0 +1,321 @@
+FRAGMENT_SHADER = """
+const float PI = 3.14159265358979323846264;
+const float THETA = 15.0 * 3.14159265358979323846264/180.0;
+
+float
+cap( int type, float dx, float dy, float t )
+{
+    float d = 0.0;
+    dx = abs(dx);
+    dy = abs(dy);
+
+    // None
+    if      (type == 0)  discard;
+    // Round
+    else if (type == 1)  d = sqrt(dx*dx+dy*dy);
+    // Triangle in
+    else if (type == 3)  d = (dx+abs(dy));
+    // Triangle out
+    else if (type == 2)  d = max(abs(dy),(t+dx-abs(dy)));
+    // Square
+    else if (type == 4)  d = max(dx,dy);
+    // Butt
+    else if (type == 5)  d = max(dx+t,dy);
+
+    return d;
+}
+
+float
+join( in int type, in float d, in vec2 segment, in vec2 texcoord,
+      in vec2 miter, in float miter_limit, in float linewidth )
+{
+    float dx = texcoord.x;
+
+    // Round join
+    // --------------------------------
+    if( type == 1 )
+    {
+        if (dx < segment.x) {
+            d = max(d,length( texcoord - vec2(segment.x,0.0)));
+            //d = length( texcoord - vec2(segment.x,0.0));
+        } else if (dx > segment.y) {
+            d = max(d,length( texcoord - vec2(segment.y,0.0)));
+            //d = length( texcoord - vec2(segment.y,0.0));
+        }
+    }
+
+    // Bevel join
+    // --------------------------------
+    else if ( type == 2 )
+    {
+        if( (dx < segment.x) ||  (dx > segment.y) )
+            d = max(d, min(abs(miter.x),abs(miter.y)));
+    }
+
+    // Miter limit
+    // --------------------------------
+    if( (dx < segment.x) ||  (dx > segment.y) )
+    {
+        d = max(d, min(abs(miter.x),
+                       abs(miter.y)) - miter_limit*linewidth/2.0 );
+    }
+
+    return d;
+}
+
+
+// Uniforms
+uniform sampler2D u_dash_atlas;
+
+// Varying
+varying vec4  v_color;
+varying vec2  v_segment;
+varying vec2  v_angles;
+varying vec2  v_linecaps;
+varying vec2  v_texcoord;
+varying vec2  v_miter;
+varying float v_miter_limit;
+varying float v_length;
+varying float v_linejoin;
+varying float v_linewidth;
+varying float v_antialias;
+varying float v_dash_phase;
+varying float v_dash_period;
+varying float v_dash_index;
+varying vec2  v_dash_caps;
+varying float v_closed;
+void main()
+{
+    // gl_FragColor = v_color; return;
+    // vec4 color = v_color;
+
+    // If color is fully transparent we just discard the fragment
+    if( v_color.a <= 0.0 ) {
+        discard;
+    }
+
+    // Test if dash pattern is the solid one (0)
+    bool solid = (v_dash_index == 0.0);
+
+    float dx = v_texcoord.x;
+    float dy = v_texcoord.y;
+    float t = v_linewidth/2.0-v_antialias;
+    float width = v_linewidth;
+    float d = 0.0;
+
+    vec2 linecaps = v_linecaps;
+    vec2 dash_caps = v_dash_caps;
+    float line_start = 0.0;
+    float line_stop  = v_length;
+    // Test if path is closed
+    bool closed = (v_closed > 0.0);
+
+    // ------------------------------------------------------------------------
+    // Solid line
+    // ------------------------------------------------------------------------
+    if( solid ) {
+        d = abs(dy);
+
+        if( (!closed) && (dx < line_start) )
+        {
+            d = cap( int(v_linecaps.x), abs(dx), abs(dy), t );
+        }
+        else if( (!closed) &&  (dx > line_stop) )
+        {
+            d = cap( int(v_linecaps.y), abs(dx)-line_stop, abs(dy), t );
+        }
+        else
+        {
+            d = join( int(v_linejoin), abs(dy), v_segment, v_texcoord,
+                      v_miter, v_miter_limit, v_linewidth );
+        }
+
+    // ------------------------------------------------------------------------
+    // Dash line
+    // ------------------------------------------------------------------------
+    } else {
+        float segment_start = v_segment.x;
+        float segment_stop  = v_segment.y;
+        float segment_center = (segment_start+segment_stop)/2.0;
+        float freq = v_dash_period*width;
+        float u = mod( dx + v_dash_phase*width,freq );
+        vec4 tex = texture2D(u_dash_atlas, vec2(u/freq, v_dash_index));
+        float dash_center= tex.x * width;
+        float dash_type  = tex.y;
+        float _start = tex.z * width;
+        float _stop  = tex.a * width;
+        float dash_start = dx - u + _start;
+        float dash_stop  = dx - u + _stop;
+
+        // This test if the we are dealing with a discontinuous angle
+        bool discont = ((dx <  segment_center) && abs(v_angles.x) > THETA) ||
+                       ((dx >= segment_center) && abs(v_angles.y) > THETA);
+        if( dx < line_start) discont = false;
+        if( dx > line_stop)  discont = false;
+
+        // When path is closed, we do not have room for linecaps, so we make
+        // room by shortening the total length
+        if (closed){
+            line_start += v_linewidth/2.0;
+            line_stop  -= v_linewidth/2.0;
+            linecaps = v_dash_caps;
+        }
+
+
+        // Check is dash stop is before line start
+        if( dash_stop <= line_start )
+        {
+            discard;
+        }
+        // Check is dash start is beyond line stop
+        if( dash_start >= line_stop )
+        {
+            discard;
+        }
+
+        // Check if current pattern start is beyond segment stop
+        if( discont )
+        {
+            // Dash start is beyond segment, we discard
+            if( dash_start > segment_stop )
+            {
+                discard;
+            }
+
+            // Dash stop is before segment, we discard
+            if( dash_stop < segment_start )
+            {
+                discard;
+            }
+
+            // Special case for round caps (nicer with this)
+            if( (u > _stop) && (dash_stop > segment_stop ) &&
+                (abs(v_angles.y) < PI/2.0))
+            {
+                if( dash_caps.x == 1.0) discard;
+            }
+            // Special case for round caps  (nicer with this)
+            else if( (u < _start) && (dash_start < segment_start ) &&
+                     (abs(v_angles.x) < PI/2.0))
+            {
+                if( dash_caps.y == 1.0) discard;
+            }
+
+            // Special case for triangle caps (in & out) and square
+            // We make sure the cap stop at crossing frontier
+            if( (dash_caps.x != 1.0) && (dash_caps.x != 5.0) )
+            {
+                if( (dash_start < segment_start ) &&
+                    (abs(v_angles.x) < PI/2.0) )
+                {
+                    float a = v_angles.x/2.0;
+                    float x = (segment_start-dx)*cos(a) - dy*sin(a);
+                    float y = (segment_start-dx)*sin(a) + dy*cos(a);
+                    if( (x > 0.0) ) discard;
+                    // We transform the cap into square to avoid holes
+                    dash_caps.x = 4.0;
+                }
+            }
+            // Special case for triangle caps (in & out) and square
+            // We make sure the cap stop at crossing frontier
+            if( (dash_caps.y != 1.0) && (dash_caps.y != 5.0) )
+            {
+                if( (dash_stop > segment_stop ) &&
+                    (abs(v_angles.y) < PI/2.0) )
+                {
+                    float a = v_angles.y/2.0;
+                    float x = (dx-segment_stop)*cos(a) - dy*sin(a);
+                    float y = (dx-segment_stop)*sin(a) + dy*cos(a);
+                    if( (x > 0.0) ) discard;
+                    // We transform the caps into square to avoid holes
+                    dash_caps.y = 4.0;
+                }
+            }
+        }
+
+        // Line cap at start
+        if( (dx < line_start) && (dash_start < line_start) &&
+            (dash_stop > line_start) )
+        {
+            d = cap( int(linecaps.x), dx-line_start, dy, t);
+        }
+        // Line cap at stop
+        else if( (dx > line_stop) && (dash_stop > line_stop) &&
+                 (dash_start < line_stop)  )
+        {
+            d = cap( int(linecaps.y), dx-line_stop, dy, t);
+        }
+        // Dash cap left
+        else if( dash_type < 0.0 )
+        {
+            float u = max( u-dash_center , 0.0 );
+            d = cap( int(dash_caps.y), abs(u), dy, t);
+        }
+        // Dash cap right
+        else if( dash_type > 0.0 )
+        {
+            float u = max( dash_center-u, 0.0 );
+            d = cap( int(dash_caps.x), abs(u), dy, t);
+        }
+        // Dash body (plain)
+        else if( dash_type == 0.0 )
+        {
+            d = abs(dy);
+        }
+
+        // Antialiasing at segment angles region
+        if( discont )
+        {
+            if( dx < segment_start )
+            {
+                // For sharp angles, we do not enforce cap shape
+                if( (dash_start < segment_start ) &&
+                    (abs(v_angles.x) > PI/2.0))
+                {
+                    d = abs(dy);
+                }
+                // Antialias at outer border
+                dx = segment_start - dx;
+                float angle = PI/2.+v_angles.x;
+                float f = abs( dx*cos(angle) - dy*sin(angle));
+                d = max(f,d);
+            }
+            else if( (dx > segment_stop) )
+            {
+                // For sharp angles, we do not enforce cap shape
+                if( (dash_stop > segment_stop ) &&
+                    (abs(v_angles.y) > PI/2.0) )
+                {
+                    d = abs(dy);
+                }
+                // Antialias at outer border
+                dx = dx - segment_stop;
+                float angle = PI/2.+v_angles.y;
+                float f = abs( dx*cos(angle) - dy*sin(angle));
+                d = max(f,d);
+            }
+        }
+
+        // Line join
+        //if( (dx > line_start) && (dx < line_stop) )
+        {
+            d = join( int(v_linejoin), d, v_segment, v_texcoord,
+                      v_miter, v_miter_limit, v_linewidth );
+        }
+    }
+
+
+    // Distance to border
+    // ------------------------------------------------------------------------
+    d = d - t;
+    if( d < 0.0 )
+    {
+        gl_FragColor = v_color;
+    }
+    else
+    {
+        d /= v_antialias;
+        gl_FragColor = vec4(v_color.xyz, exp(-d*d)*v_color.a);
+    }
+}
+"""
diff --git a/vispy/scene/visuals/line/line.py b/vispy/scene/visuals/line/line.py
new file mode 100644
index 0000000..31174a5
--- /dev/null
+++ b/vispy/scene/visuals/line/line.py
@@ -0,0 +1,424 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Line visual implementing Agg- and GL-based drawing modes.
+"""
+
+from __future__ import division
+
+import numpy as np
+
+from .... import gloo
+from ....color import ColorArray
+from ...shaders import ModularProgram, Function
+from ..visual import Visual
+
+from .dash_atlas import DashAtlas
+from .vertex import VERTEX_SHADER as AGG_VERTEX_SHADER
+from .fragment import FRAGMENT_SHADER as AGG_FRAGMENT_SHADER
+
+
+vec2to4 = Function("""
+    vec4 vec2to4(vec2 input) {
+        return vec4(input, 0, 1);
+    }
+""")
+
+vec3to4 = Function("""
+    vec4 vec3to4(vec3 input) {
+        return vec4(input, 1);
+    }
+""")
+
+
+"""
+TODO:
+
+* Agg support is very minimal; needs attention.
+* Optimization--avoid creating new buffers, avoid triggering program
+  recompile.
+"""
+
+
+joins = {'miter': 0, 'round': 1, 'bevel': 2}
+
+caps = {'': 0, 'none': 0, '.': 0,
+        'round': 1, ')': 1, '(': 1, 'o': 1,
+        'triangle in': 2, '<': 2,
+        'triangle out': 3, '>': 3,
+        'square': 4, '=': 4, 'butt': 4,
+        '|': 5}
+
+_agg_vtype = np.dtype([('a_position', 'f4', 2),
+                       ('a_tangents', 'f4', 4),
+                       ('a_segment',  'f4', 2),
+                       ('a_angles',   'f4', 2),
+                       ('a_texcoord', 'f4', 2),
+                       ('alength', 'f4', 1),
+                       ('color', 'f4', 4)])
+
+
+def _agg_bake(vertices, color, closed=False):
+    """
+    Bake a list of 2D vertices for rendering them as thick line. Each line
+    segment must have its own vertices because of antialias (this means no
+    vertex sharing between two adjacent line segments).
+    """
+
+    n = len(vertices)
+    P = np.array(vertices).reshape(n, 2).astype(float)
+    idx = np.arange(n)  # used to eventually tile the color array
+
+    dx, dy = P[0] - P[-1]
+    d = np.sqrt(dx*dx+dy*dy)
+
+    # If closed, make sure first vertex = last vertex (+/- epsilon=1e-10)
+    if closed and d > 1e-10:
+        P = np.append(P, P[0]).reshape(n+1, 2)
+        idx = np.append(idx, idx[-1])
+        n += 1
+
+    V = np.zeros(len(P), dtype=_agg_vtype)
+    V['a_position'] = P
+
+    # Tangents & norms
+    T = P[1:] - P[:-1]
+
+    N = np.sqrt(T[:, 0]**2 + T[:, 1]**2)
+    # T /= N.reshape(len(T),1)
+    V['a_tangents'][+1:, :2] = T
+    V['a_tangents'][0, :2] = T[-1] if closed else T[0]
+    V['a_tangents'][:-1, 2:] = T
+    V['a_tangents'][-1, 2:] = T[0] if closed else T[-1]
+
+    # Angles
+    T1 = V['a_tangents'][:, :2]
+    T2 = V['a_tangents'][:, 2:]
+    A = np.arctan2(T1[:, 0]*T2[:, 1]-T1[:, 1]*T2[:, 0],
+                   T1[:, 0]*T2[:, 0]+T1[:, 1]*T2[:, 1])
+    V['a_angles'][:-1, 0] = A[:-1]
+    V['a_angles'][:-1, 1] = A[+1:]
+
+    # Segment
+    L = np.cumsum(N)
+    V['a_segment'][+1:, 0] = L
+    V['a_segment'][:-1, 1] = L
+    #V['a_lengths'][:,2] = L[-1]
+
+    # Step 1: A -- B -- C  =>  A -- B, B' -- C
+    V = np.repeat(V, 2, axis=0)[1:-1]
+    V['a_segment'][1:] = V['a_segment'][:-1]
+    V['a_angles'][1:] = V['a_angles'][:-1]
+    V['a_texcoord'][0::2] = -1
+    V['a_texcoord'][1::2] = +1
+    idx = np.repeat(idx, 2)[1:-1]
+
+    # Step 2: A -- B, B' -- C  -> A0/A1 -- B0/B1, B'0/B'1 -- C0/C1
+    V = np.repeat(V, 2, axis=0)
+    V['a_texcoord'][0::2, 1] = -1
+    V['a_texcoord'][1::2, 1] = +1
+    idx = np.repeat(idx, 2)
+
+    I = np.resize(np.array([0, 1, 2, 1, 2, 3], dtype=np.uint32), (n-1)*(2*3))
+    I += np.repeat(4*np.arange(n-1, dtype=np.uint32), 6)
+
+    # Length
+    V['alength'] = L[-1] * np.ones(len(V))
+
+    # Color
+    if color.ndim == 1:
+        color = np.tile(color, (len(V), 1))
+    elif color.ndim == 2 and len(color) == n:
+        color = color[idx]
+    else:
+        raise ValueError('Color length %s does not match number of vertices '
+                         '%s' % (len(color), n))
+    V['color'] = color
+
+    return gloo.VertexBuffer(V), gloo.IndexBuffer(I)
+
+
+GL_VERTEX_SHADER = """
+    varying vec4 v_color;
+
+    void main(void)
+    {
+        gl_Position = $transform($position);
+        v_color = $color;
+    }
+"""
+
+GL_FRAGMENT_SHADER = """
+    varying vec4 v_color;
+    void main()
+    {
+        gl_FragColor = v_color;
+    }
+"""
+
+
+class Line(Visual):
+    """Line visual
+
+    Parameters
+    ----------
+    pos : array
+        Array of shape (..., 2) or (..., 3) specifying vertex coordinates.
+    color : Color, tuple, or array
+        The color to use when drawing the line. If an array is given, it
+        must be of shape (..., 4) and provide one rgba color per vertex.
+    width:
+        The width of the line in px. Line widths > 1px are only
+        guaranteed to work when using 'agg' mode.
+    connect : str or array
+        Determines which vertices are connected by lines.
+            * "strip" causes the line to be drawn with each vertex
+              connected to the next.
+            * "segments" causes each pair of vertices to draw an
+              independent line segment
+            * numpy arrays specify the exact set of segment pairs to
+              connect.
+    mode : str
+        Mode to use for drawing.
+            * "agg" uses anti-grain geometry to draw nicely antialiased lines
+              with proper joins and endcaps.
+            * "gl" uses OpenGL's built-in line rendering. This is much faster,
+              but produces much lower-quality results and is not guaranteed to
+              obey the requested line width or join/endcap styles.
+    antialias : bool 
+        For mode='gl', specifies whether to use line smoothing or not.
+    """
+    def __init__(self, pos=None, color=(0.5, 0.5, 0.5, 1), width=1,
+                 connect='strip', mode='gl', antialias=False, **kwds):
+        # todo: Get rid of aa argument? It's a bit awkward since ...
+        # - line_smooth is not supported on ES 2.0
+        # - why on earth would you turn off aa with agg?
+        Visual.__init__(self, **kwds)
+        self._pos = pos
+        self._color = ColorArray(color)
+        self._width = float(width)
+        assert connect is not None  # can't be to start
+        self._connect = connect
+        self._mode = 'none'
+        self._origs = {}
+        self.antialias = antialias
+        self._vbo = None
+        self._I = None
+        # Set up the GL program
+        self._gl_program = ModularProgram(GL_VERTEX_SHADER,
+                                          GL_FRAGMENT_SHADER)
+        # Set up the AGG program
+        self._agg_program = ModularProgram(AGG_VERTEX_SHADER,
+                                           AGG_FRAGMENT_SHADER)
+        # agg attributes
+        self._da = None
+        self._U = None
+        self._dash_atlas = None
+        
+        # now actually set the mode, which will call set_data
+        self.mode = mode
+
+    @property
+    def antialias(self):
+        return self._antialias
+
+    @antialias.setter
+    def antialias(self, aa):
+        self._antialias = bool(aa)
+        self.update()
+
+    @property
+    def mode(self):
+        """The current drawing mode"""
+        return self._mode
+
+    @mode.setter
+    def mode(self, mode):
+        if mode not in ('agg', 'gl'):
+            raise ValueError('mode argument must be "agg" or "gl".')
+        if mode == self._mode:
+            return
+        # If the mode changed, reset everything
+        self._mode = mode
+        if self._mode == 'agg' and self._da is None:
+            self._da = DashAtlas()
+            dash_index, dash_period = self._da['solid']
+            self._U = dict(dash_index=dash_index, dash_period=dash_period,
+                           linejoin=joins['round'],
+                           linecaps=(caps['round'], caps['round']),
+                           dash_caps=(caps['round'], caps['round']),
+                           linewidth=self._width, antialias=1.0)
+            self._dash_atlas = gloo.Texture2D(self._da._data)
+            
+        # do not call subclass set_data; this is often overridden with a 
+        # different signature.
+        Line.set_data(self, self._pos, self._color, self._width, self._connect)
+
+    def set_data(self, pos=None, color=None, width=None, connect=None):
+        """ Set the data used to draw this visual.
+
+        Parameters
+        ----------
+        pos : array
+            Array of shape (..., 2) or (..., 3) specifying vertex coordinates.
+        color : Color, tuple, or array
+            The color to use when drawing the line. If an array is given, it
+            must be of shape (..., 4) and provide one rgba color per vertex.
+        width:
+            The width of the line in px. Line widths > 1px are only
+            guaranteed to work when using 'agg' mode.
+        connect : str or array
+            Determines which vertices are connected by lines.
+            * "strip" causes the line to be drawn with each vertex
+              connected to the next.
+            * "segments" causes each pair of vertices to draw an
+              independent line segment
+            * int numpy arrays specify the exact set of segment pairs to
+              connect.
+            * bool numpy arrays specify which _adjacent_ pairs to connect.
+        """
+        if isinstance(connect, np.ndarray) and connect.dtype == bool:
+            connect = self._convert_bool_connect(connect)
+        
+        self._origs = {'pos': pos, 'color': color, 
+                       'width': width, 'connect': connect}
+        
+        if color is not None:
+            self._color = ColorArray(color).rgba
+            if len(self._color) == 1:
+                self._color = self._color[0]
+                
+        if width is not None:
+            self._width = width
+            
+        if self.mode == 'gl':
+            self._gl_set_data(**self._origs)
+        else:
+            self._agg_set_data(**self._origs)
+
+    def _convert_bool_connect(self, connect):
+        # Convert a boolean connection array to a vertex index array
+        assert connect.ndim == 1
+        index = np.empty((len(connect), 2), dtype=np.uint32)
+        index[:] = np.arange(len(connect))[:, np.newaxis]
+        index[:, 1] += 1
+        return index[connect]
+            
+    def _gl_set_data(self, pos, color, width, connect):
+        if connect is not None:
+            if isinstance(connect, np.ndarray):
+                self._connect = gloo.IndexBuffer(connect.astype(np.uint32))
+            else:
+                self._connect = connect
+        if pos is not None:
+            self._pos = pos
+            pos_arr = np.asarray(pos, dtype=np.float32)
+            vbo = gloo.VertexBuffer(pos_arr)
+            if pos_arr.shape[-1] == 2:
+                self._pos_expr = vec2to4(vbo)
+            elif pos_arr.shape[-1] == 3:
+                self._pos_expr = vec3to4(vbo)
+            else:
+                raise TypeError("pos array should have 2 or 3 elements in last"
+                                " axis. shape=%r" % pos_arr.shape)
+            self._vbo = vbo
+        else:
+            self._pos = None
+        self.update()
+
+    def _agg_set_data(self, pos, color, width, connect):
+        if connect is not None:
+            if connect != 'strip':
+                raise NotImplementedError("Only 'strip' connection mode "
+                                          "allowed for agg-mode lines.")
+            self._connect = connect
+        if pos is not None:
+            self._pos = pos
+            self._vbo, self._I = _agg_bake(pos, self._color)
+        else:
+            self._pos = None
+
+        self.update()
+
+    def bounds(self, mode, axis):
+        if 'pos' not in self._origs:
+            return None
+        data = self._origs['pos']
+        if data.shape[1] > axis:
+            return (data[:, axis].min(), data[:, axis].max())
+        else:
+            return (0, 0)
+
+    def draw(self, event):
+        if self.mode == 'gl':
+            self._gl_draw(event)
+        else:
+            self._agg_draw(event)
+
+    def _gl_draw(self, event):
+        if self._pos is None:
+            return
+        xform = event.render_transform.shader_map()
+        self._gl_program.vert['transform'] = xform
+        self._gl_program.vert['position'] = self._pos_expr
+        if self._color.ndim == 1:
+            self._gl_program.vert['color'] = self._color
+        else:
+            self._gl_program.vert['color'] = gloo.VertexBuffer(self._color)
+        gloo.set_state('translucent')
+        
+        # Do we want to use OpenGL, and can we?
+        GL = None
+        if self._width > 1 or self._antialias:
+            try:
+                import OpenGL.GL as GL
+            except ImportError:
+                pass
+        
+        # Turn on line smooth and/or line width
+        if GL:
+            if self._antialias:
+                GL.glEnable(GL.GL_LINE_SMOOTH)
+            if GL and self._width > 1:
+                GL.glLineWidth(self._width)
+        
+        # Draw
+        if self._connect == 'strip':
+            self._gl_program.draw('line_strip')
+        elif self._connect == 'segments':
+            self._gl_program.draw('lines')
+        elif isinstance(self._connect, gloo.IndexBuffer):
+            self._gl_program.draw('lines', self._connect)
+        else:
+            raise ValueError("Invalid line connect mode: %r" % self._connect)
+        
+        # Turn off line smooth and/or line width
+        if GL:
+            if self._antialias:
+                GL.glDisable(GL.GL_LINE_SMOOTH)
+            if GL and self._width > 1:
+                GL.glLineWidth(1)
+
+    def _agg_draw(self, event):
+        if self._pos is None:
+            return
+        gloo.set_state('translucent', depth_test=False)
+        data_doc = event.document_transform()
+        doc_px = event.entity_transform(map_from=event.document_cs,
+                                        map_to=event.framebuffer_cs)
+        px_ndc = event.entity_transform(map_from=event.framebuffer_cs,
+                                        map_to=event.render_cs)
+        vert = self._agg_program.vert
+        vert['doc_px_transform'] = doc_px.shader_map()
+        vert['px_ndc_transform'] = px_ndc.shader_map()
+        vert['transform'] = data_doc.shader_map()
+        self._agg_program.prepare()
+        self._agg_program.bind(self._vbo)
+        uniforms = dict(closed=False, miter_limit=4.0, dash_phase=0.0)
+        for n, v in uniforms.items():
+            self._agg_program[n] = v
+        for n, v in self._U.items():
+            self._agg_program[n] = v
+        self._agg_program['u_dash_atlas'] = self._dash_atlas
+        self._agg_program.draw('triangles', self._I)
diff --git a/vispy/scene/visuals/line/vertex.py b/vispy/scene/visuals/line/vertex.py
new file mode 100644
index 0000000..4387b68
--- /dev/null
+++ b/vispy/scene/visuals/line/vertex.py
@@ -0,0 +1,251 @@
+VERTEX_SHADER = """
+const float PI = 3.14159265358979323846264;
+const float THETA = 15.0 * 3.14159265358979323846264/180.0;
+
+// Cross product of v1 and v2
+float cross(in vec2 v1, in vec2 v2) {
+    return v1.x*v2.y - v1.y*v2.x;
+}
+
+// Returns distance of v3 to line v1-v2
+float signed_distance(in vec2 v1, in vec2 v2, in vec2 v3) {
+    return cross(v2-v1,v1-v3) / length(v2-v1);
+}
+
+// Rotate v around origin
+void rotate( in vec2 v, in float alpha, out vec2 result ) {
+    float c = cos(alpha);
+    float s = sin(alpha);
+    result = vec2( c*v.x - s*v.y,
+                   s*v.x + c*v.y );
+}
+
+vec2 transform_vector(vec2 x, vec2 base) {
+    vec4 o = $transform(vec4(base, 0, 1));
+    return ($transform(vec4(base+x, 0, 1)) - o).xy;
+}
+
+
+
+// Uniforms
+//uniform mat4 u_matrix;
+//uniform mat4 u_view;
+
+attribute vec4 color;
+// uniform vec2 u_scale;
+// uniform vec2 tr_scale;
+uniform float linewidth;
+uniform float antialias;
+uniform vec2 linecaps;
+uniform float linejoin;
+uniform float miter_limit;
+attribute float alength;
+uniform float dash_phase;
+uniform float dash_period;
+uniform float dash_index;
+uniform vec2 dash_caps;
+uniform float closed;
+
+
+// Attributes
+attribute vec2 a_position; // position of each vertex
+attribute vec4 a_tangents; // vector pointing from one vertex to the next
+attribute vec2 a_segment;  // distance along path
+attribute vec2 a_angles;
+attribute vec2 a_texcoord;
+
+// Varying
+varying vec4  v_color;
+varying vec2  v_segment;
+varying vec2  v_angles;
+varying vec2  v_linecaps;
+varying vec2  v_texcoord;
+varying vec2  v_miter;
+varying float v_miter_limit;
+varying float v_length;
+varying float v_linejoin;
+varying float v_linewidth;
+varying float v_antialias;
+varying float v_dash_phase;
+varying float v_dash_period;
+varying float v_dash_index;
+varying vec2  v_dash_caps;
+varying float v_closed;
+void main()
+{
+    v_color = color;
+
+    v_linewidth = linewidth;
+    v_antialias = antialias;
+    v_linecaps  = linecaps;
+
+    v_linejoin    = linejoin;
+    v_miter_limit = miter_limit;
+    v_length      = alength;
+    v_dash_phase  = dash_phase;
+
+    v_dash_period = dash_period;
+    v_dash_index  = dash_index;
+    v_dash_caps   = dash_caps;
+
+    v_closed = closed;
+    bool closed = (v_closed > 0.0);
+
+    // Attributes to varyings
+    v_angles  = a_angles;
+    //v_segment = a_segment * u_scale.x * tr_scale.x;  // TODO: proper scaling
+    //v_length  = v_length * u_scale * tr_scale;  // TODO: proper scaling
+    v_segment = a_segment;
+
+    // Thickness below 1 pixel are represented using a 1 pixel thickness
+    // and a modified alpha
+    v_color.a = min(v_linewidth, v_color.a);
+    v_linewidth = max(v_linewidth, 1.0);
+
+    // If color is fully transparent we just will discard the fragment anyway
+    if( v_color.a <= 0.0 )
+    {
+        gl_Position = vec4(0.0,0.0,0.0,1.0);
+        return;
+    }
+
+    // This is the actual half width of the line
+    // TODO: take care of logical - physical pixel difference here.
+    float w = ceil(1.25*v_antialias+v_linewidth)/2.0;
+
+    //vec2 position = $transform(vec4(a_position,0.,1.)).xy*u_scale;
+    vec2 position = $transform(vec4(a_position,0.,1.)).xy;
+    // At this point, position must be in _doc_ coordinates because the line
+    // width will be added to it.
+
+    //vec2 t1 = normalize(tr_scale*a_tangents.xy);
+    //vec2 t2 = normalize(tr_scale*a_tangents.zw);
+    vec2 t1 = normalize(transform_vector(a_tangents.xy, a_position));
+    vec2 t2 = normalize(transform_vector(a_tangents.zw, a_position));
+    float u = a_texcoord.x;
+    float v = a_texcoord.y;
+    vec2 o1 = vec2( +t1.y, -t1.x);
+    vec2 o2 = vec2( +t2.y, -t2.x);
+
+
+    // This is a join
+    // ----------------------------------------------------------------
+    if( t1 != t2 ) {
+        float angle  = atan (t1.x*t2.y-t1.y*t2.x, t1.x*t2.x+t1.y*t2.y);
+        vec2 t  = normalize(t1+t2);
+        vec2 o  = vec2( + t.y, - t.x);
+
+        if ( v_dash_index > 0.0 )
+        {
+            // Broken angle
+            // ----------------------------------------------------------------
+            if( (abs(angle) > THETA) ) {
+                position += v * w * o / cos(angle/2.0);
+                float s = sign(angle);
+                if( angle < 0.0 ) {
+                    if( u == +1.0 ) {
+                        u = v_segment.y + v * w * tan(angle/2.0);
+                        if( v == 1.0 ) {
+                            position -= 2.0 * w * t1 / sin(angle);
+                            u -= 2.0 * w / sin(angle);
+                        }
+                    } else {
+                        u = v_segment.x - v * w * tan(angle/2.0);
+                        if( v == 1.0 ) {
+                            position += 2.0 * w * t2 / sin(angle);
+                            u += 2.0*w / sin(angle);
+                        }
+                    }
+                } else {
+                    if( u == +1.0 ) {
+                        u = v_segment.y + v * w * tan(angle/2.0);
+                        if( v == -1.0 ) {
+                            position += 2.0 * w * t1 / sin(angle);
+                            u += 2.0 * w / sin(angle);
+                        }
+                    } else {
+                        u = v_segment.x - v * w * tan(angle/2.0);
+                        if( v == -1.0 ) {
+                            position -= 2.0 * w * t2 / sin(angle);
+                            u -= 2.0*w / sin(angle);
+                        }
+                    }
+                }
+                // Continuous angle
+                // ------------------------------------------------------------
+            } else {
+                position += v * w * o / cos(angle/2.0);
+                if( u == +1.0 ) u = v_segment.y;
+                else            u = v_segment.x;
+            }
+        }
+
+        // Solid line
+        // --------------------------------------------------------------------
+        else
+        {
+            position.xy += v * w * o / cos(angle/2.0);
+            if( angle < 0.0 ) {
+                if( u == +1.0 ) {
+                    u = v_segment.y + v * w * tan(angle/2.0);
+                } else {
+                    u = v_segment.x - v * w * tan(angle/2.0);
+                }
+            } else {
+                if( u == +1.0 ) {
+                    u = v_segment.y + v * w * tan(angle/2.0);
+                } else {
+                    u = v_segment.x - v * w * tan(angle/2.0);
+                }
+            }
+        }
+
+    // This is a line start or end (t1 == t2)
+    // ------------------------------------------------------------------------
+    } else {
+        position += v * w * o1;
+        if( u == -1.0 ) {
+            u = v_segment.x - w;
+            position -=  w * t1;
+        } else {
+            u = v_segment.y + w;
+            position +=  w * t2;
+        }
+    }
+
+    // Miter distance
+    // ------------------------------------------------------------------------
+    vec2 t;
+    //vec2 curr = $transform(vec4(a_position,0.,1.)).xy*u_scale;
+    vec2 curr = $transform(vec4(a_position,0.,1.)).xy;
+    if( a_texcoord.x < 0.0 ) {
+        vec2 next = curr + t2*(v_segment.y-v_segment.x);
+
+        rotate( t1, +a_angles.x/2.0, t);
+        v_miter.x = signed_distance(curr, curr+t, position);
+
+        rotate( t2, +a_angles.y/2.0, t);
+        v_miter.y = signed_distance(next, next+t, position);
+    } else {
+        vec2 prev = curr - t1*(v_segment.y-v_segment.x);
+
+        rotate( t1, -a_angles.x/2.0,t);
+        v_miter.x = signed_distance(prev, prev+t, position);
+
+        rotate( t2, -a_angles.y/2.0,t);
+        v_miter.y = signed_distance(curr, curr+t, position);
+    }
+
+    if (!closed && v_segment.x <= 0.0) {
+        v_miter.x = 1e10;
+    }
+    if (!closed && v_segment.y >= v_length)
+    {
+        v_miter.y = 1e10;
+    }
+
+    v_texcoord = vec2( u, v*w );
+    gl_Position = $px_ndc_transform($doc_px_transform(vec4(position,
+                                                           0.0, 1.0)));
+}
+"""
diff --git a/vispy/scene/visuals/line_plot.py b/vispy/scene/visuals/line_plot.py
new file mode 100644
index 0000000..318be4b
--- /dev/null
+++ b/vispy/scene/visuals/line_plot.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+
+from .line import Line
+from .markers import Markers
+from .visual import Visual
+
+
+class LinePlot(Visual):
+    """Visual displaying a plot line with optional markers.
+
+    Parameters
+    ----------
+    *args : array | two arrays
+        Arguments can be passed as (Y,), (X, Y) or (np.array((X, Y))).
+    **kwargs : keyword arguments
+        Keyword arguments to pass on to the Line and Marker visuals.
+        Supported arguments are width, connect, color, edge_color, face_color,
+        and edge_width.
+
+    Examples
+    --------
+    All of these syntaxes will work:
+
+        >>> LinePlot(y_vals)
+        >>> LinePlot(x_vals, y_vals)
+        >>> LinePlot(xy_vals)
+
+    See also
+    --------
+    Line, Markers
+    """
+    _line_kwds = ['width', 'connect', 'color']
+    _marker_kwds = ['edge_color', 'face_color', 'edge_width']
+
+    def __init__(self, *args, **kwds):
+        my_kwds = {}
+        for k in self._line_kwds + self._marker_kwds:
+            if k in kwds:
+                my_kwds[k] = kwds.pop(k)
+
+        Visual.__init__(self, **kwds)
+        self._line = Line()
+        self._markers = Markers()
+
+        self.set_data(*args, **my_kwds)
+
+    def set_data(self, *args, **kwds):
+        args = [np.array(x) for x in args]
+
+        if len(args) == 1:
+            arg = args[0]
+            if arg.ndim == 2:
+                # xy array already provided
+                pos = arg
+            elif arg.ndim == 1:
+                # only y supplied, generate arange x
+                pos = np.empty((len(arg), 2), dtype=np.float32)
+                pos[:, 1] = arg
+                pos[:, 0] = np.arange(len(arg))
+            else:
+                raise TypeError("Invalid argument: array must have ndim "
+                                "<= 2.")
+        elif len(args) == 2:
+            pos = np.concatenate([args[0][:, np.newaxis],
+                                  args[1][:, np.newaxis]], axis=1)
+        else:
+            raise TypeError("Too many positional arguments given (max is 2).")
+
+        # todo: have both sub-visuals share the same buffers.
+        line_kwds = {}
+        for k in self._line_kwds:
+            if k in kwds:
+                line_kwds[k] = kwds.pop(k)
+        self._line.set_data(pos=pos, **line_kwds)
+        marker_kwds = {}
+        for k in self._marker_kwds:
+            if k in kwds:
+                marker_kwds[k] = kwds.pop(k)
+        self._markers.set_data(pos=pos, **marker_kwds)
+        if len(kwds) > 0:
+            raise TypeError("Invalid keyword arguments: %s" % kwds.keys())
+
+    def bounds(self, mode, axis):
+        return self._line.bounds(mode, axis)
+
+    def draw(self, event):
+        for v in self._line, self._markers:
+            event.push_entity(v)
+            try:
+                v.draw(event)
+            finally:
+                event.pop_entity()
diff --git a/vispy/scene/visuals/markers.py b/vispy/scene/visuals/markers.py
new file mode 100644
index 0000000..6b333e2
--- /dev/null
+++ b/vispy/scene/visuals/markers.py
@@ -0,0 +1,334 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+Marker Visual and shader definitions.
+"""
+
+import numpy as np
+
+from ...color import Color
+from ...gloo import set_state, VertexBuffer, _check_valid
+from ..shaders import ModularProgram, Function, Variable
+from .visual import Visual
+
+
+vert = """
+uniform mat4 u_projection;
+uniform float u_antialias;
+
+attribute vec3  a_position;
+attribute vec4  a_fg_color;
+attribute vec4  a_bg_color;
+attribute float a_edgewidth;
+attribute float a_size;
+
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_edgewidth;
+varying float v_antialias;
+
+void main (void) {
+    $v_size = a_size;
+    v_edgewidth = a_edgewidth;
+    v_antialias = u_antialias;
+    v_fg_color  = a_fg_color;
+    v_bg_color  = a_bg_color;
+    gl_Position = $transform(vec4(a_position,1.0));
+    gl_PointSize = $v_size + 2*(v_edgewidth + 1.5*v_antialias);
+}
+"""
+
+
+frag = """
+varying vec4 v_fg_color;
+varying vec4 v_bg_color;
+varying float v_edgewidth;
+varying float v_antialias;
+
+void main()
+{
+    float size = $v_size +2*(v_edgewidth + 1.5*v_antialias);
+    float t = v_edgewidth/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_edgewidth/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 disc(vec2 pointcoord, float size)
+{
+    float r = length((pointcoord.xy - vec2(0.5,0.5))*size);
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+
+arrow = """
+float arrow(vec2 pointcoord, float size)
+{
+    float r1 = abs(pointcoord.x -.50)*size +
+               abs(pointcoord.y -.5)*size - $v_size/2;
+    float r2 = abs(pointcoord.x -.25)*size +
+               abs(pointcoord.y -.5)*size - $v_size/2;
+    float r = max(r1,-r2);
+    return r;
+}
+"""
+
+
+ring = """
+float ring(vec2 pointcoord, float size)
+{
+    float r1 = length((pointcoord.xy - vec2(0.5,0.5))*size) - $v_size/2;
+    float r2 = length((pointcoord.xy - vec2(0.5,0.5))*size) - $v_size/4;
+    float r = max(r1,-r2);
+    return r;
+}
+"""
+
+
+clobber = """
+float clobber(vec2 pointcoord, 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((pointcoord.xy- vec2(0.5,0.5) - c1)*size);
+    r1 -= $v_size/3;
+    float r2 = length((pointcoord.xy- vec2(0.5,0.5) - c2)*size);
+    r2 -= $v_size/3;
+    float r3 = length((pointcoord.xy- vec2(0.5,0.5) - c3)*size);
+    r3 -= $v_size/3;
+    float r = min(min(r1,r2),r3);
+    return r;
+}
+"""
+
+
+square = """
+float square(vec2 pointcoord, float size)
+{
+    float r = max(abs(pointcoord.x -.5)*size, abs(pointcoord.y -.5)*size);
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+
+x_ = """
+float x_(vec2 pointcoord, float size)
+{
+    vec2 rotcoord = vec2((pointcoord.x + pointcoord.y - 1.) / sqrt(2.),
+                         (pointcoord.y - pointcoord.x) / sqrt(2.));
+    float r1 = max(abs(rotcoord.x - 0.25)*size,
+                   abs(rotcoord.x + 0.25)*size);
+    float r2 = max(abs(rotcoord.y - 0.25)*size,
+                   abs(rotcoord.y + 0.25)*size);
+    float r3 = max(abs(rotcoord.x)*size,
+                   abs(rotcoord.y)*size);
+    float r = max(min(r1,r2),r3);
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+
+diamond = """
+float diamond(vec2 pointcoord, float size)
+{
+    float r = abs(pointcoord.x -.5)*size + abs(pointcoord.y -.5)*size;
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+
+vbar = """
+float vbar(vec2 pointcoord, float size)
+{
+    float r1 = max(abs(pointcoord.x - 0.75)*size,
+                   abs(pointcoord.x - 0.25)*size);
+    float r3 = max(abs(pointcoord.x - 0.50)*size,
+                   abs(pointcoord.y - 0.50)*size);
+    float r = max(r1,r3);
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+
+hbar = """
+float hbar(vec2 pointcoord, float size)
+{
+    float r2 = max(abs(pointcoord.y - 0.75)*size,
+                   abs(pointcoord.y - 0.25)*size);
+    float r3 = max(abs(pointcoord.x - 0.50)*size,
+                   abs(pointcoord.y - 0.50)*size);
+    float r = max(r2,r3);
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+
+cross = """
+float cross(vec2 pointcoord, float size)
+{
+    float r1 = max(abs(pointcoord.x - 0.75)*size,
+                   abs(pointcoord.x - 0.25)*size);
+    float r2 = max(abs(pointcoord.y - 0.75)*size,
+                   abs(pointcoord.y - 0.25)*size);
+    float r3 = max(abs(pointcoord.x - 0.50)*size,
+                   abs(pointcoord.y - 0.50)*size);
+    float r = max(min(r1,r2),r3);
+    r -= $v_size/2;
+    return r;
+}
+"""
+
+tailed_arrow = """
+float tailed_arrow(vec2 pointcoord, float size)
+{
+    //arrow_right
+    float r1 = abs(pointcoord.x -.50)*size +
+               abs(pointcoord.y -.5)*size - $v_size/2;
+    float r2 = abs(pointcoord.x -.25)*size +
+               abs(pointcoord.y -.5)*size - $v_size/2;
+    float arrow = max(r1,-r2);
+
+    //hbar
+    float r3 = (abs(pointcoord.y-.5)*2+.3)*$v_size-$v_size/2;
+    float r4 = (pointcoord.x -.775)*size;
+    float r6 = abs(pointcoord.x -.5)*size-$v_size/2;
+    float limit = (pointcoord.x -.5)*size +
+                  abs(pointcoord.y -.5)*size - $v_size/2;
+    float hbar = max(limit,max(max(r3,r4),r6));
+
+    return min(arrow,hbar);
+}
+"""
+
+_marker_dict = {
+    'disc': disc,
+    'arrow': arrow,
+    'ring': ring,
+    'clobber': clobber,
+    'square': square,
+    'diamond': diamond,
+    'vbar': vbar,
+    'hbar': hbar,
+    'cross': cross,
+    'tailed_arrow': tailed_arrow,
+    'x': x_,
+    # aliases
+    'o': disc,
+    '+': cross,
+    's': square,
+    '-': hbar,
+    '|': vbar,
+    '->': tailed_arrow,
+    '>': arrow,
+}
+marker_types = tuple(sorted(list(_marker_dict.keys())))
+
+
+class Markers(Visual):
+    """ Visual displaying marker symbols. 
+    """
+    def __init__(self):
+        self._program = ModularProgram(vert, frag)
+        self._v_size_var = Variable('varying float v_size')
+        self._program.vert['v_size'] = self._v_size_var
+        self._program.frag['v_size'] = self._v_size_var
+        Visual.__init__(self)
+
+    def set_data(self, pos=None, style='o', size=10., edge_width=1.,
+                 edge_color='black', face_color='white'):
+        """ Set the data used to display this visual.
+        
+        Parameters
+        ----------
+        pos : array
+            The array of locations to display each symbol.
+        style : str
+            The style of symbol to draw (see Notes).
+        size : float
+            The symbol size in px.
+        edge_width : float
+            The width of the symbol outline in px.
+        edge_color : Color
+            The color used to draw the symbol outline.
+        face_color : Color
+            The color used to draw the symbol interior.
+            
+        Notes
+        -----
+        
+        Allowed style strings are: disc, arrow, ring, clobber, square, diamond,
+        vbar, hbar, cross, tailed_arrow, and x.
+        """
+        assert (isinstance(pos, np.ndarray) and
+                pos.ndim == 2 and pos.shape[1] in (2, 3))
+        assert edge_width > 0
+        self.set_style(style)
+        edge_color = Color(edge_color).rgba
+        face_color = Color(face_color).rgba
+        n = len(pos)
+        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_edgewidth', np.float32, 1)])
+        data['a_fg_color'] = edge_color
+        data['a_bg_color'] = face_color
+        data['a_edgewidth'] = edge_width
+        data['a_position'][:, :pos.shape[1]] = pos
+        data['a_size'] = size
+        self._vbo = VertexBuffer(data)
+
+    def set_style(self, style='o'):
+        _check_valid('style', style, marker_types)
+        self._marker_fun = Function(_marker_dict[style])
+        self._marker_fun['v_size'] = self._v_size_var
+        self._program.frag['marker'] = self._marker_fun
+
+    def draw(self, event=None):
+        set_state(depth_test=False, blend=True, clear_color='white',
+                  blend_func=('src_alpha', 'one_minus_src_alpha'))
+        if event is not None:
+            xform = event.render_transform.shader_map()
+            self._program.vert['transform'] = xform
+        self._program.prepare()
+        self._program['u_antialias'] = 1
+        self._program.bind(self._vbo)
+        self._program.draw('points')
diff --git a/vispy/scene/visuals/mesh.py b/vispy/scene/visuals/mesh.py
new file mode 100644
index 0000000..09b698e
--- /dev/null
+++ b/vispy/scene/visuals/mesh.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+""" A Mesh Visual that uses the new shader Function.
+"""
+
+from __future__ import division
+
+import numpy as np
+
+from .visual import Visual
+from ..shaders import ModularProgram, Function, Varying
+from ...gloo import VertexBuffer, IndexBuffer, set_state
+from ...geometry import MeshData
+from ...color import Color
+
+## Snippet templates (defined as string to force user to create fresh Function)
+# Consider these stored in a central location in vispy ...
+
+
+vertex_template = """
+
+void main() {
+   gl_Position = $transform($position);
+}
+"""
+
+fragment_template = """
+void main() {
+  gl_FragColor = $color;
+}
+"""
+
+phong_template = """
+vec4 phong_shading(vec4 color) {
+    vec4 o = $transform(vec4(0, 0, 0, 1));
+    vec4 n = $transform(vec4($normal, 1));
+    vec3 norm = normalize((n-o).xyz);
+    vec3 light = normalize($light_dir.xyz);
+    float p = dot(light, norm);
+    p = (p < 0. ? 0. : p);
+    vec4 diffuse = $light_color * p;
+    diffuse.a = 1.0;
+    p = dot(reflect(light, norm), vec3(0,0,1));
+    if (p < 0.0) {
+        p = 0.0;
+    }
+    vec4 specular = $light_color * 5.0 * pow(p, 100.);
+    return color * ($ambient + diffuse) + specular;
+}
+"""
+
+## Functions that can be used as is (don't have template variables)
+# Consider these stored in a central location in vispy ...
+
+vec3to4 = Function("""
+vec4 vec3to4(vec3 xyz) {
+    return vec4(xyz, 1.0);
+}
+""")
+
+vec2to4 = Function("""
+vec4 vec2to4(vec2 xyz) {
+    return vec4(xyz, 0.0, 1.0);
+}
+""")
+
+
+class Mesh(Visual):
+
+    def __init__(self, vertices=None, faces=None, vertex_colors=None,
+                 face_colors=None, color=(0.5, 0.5, 1, 1), meshdata=None,
+                 shading=None, mode='triangles', **kwds):
+        Visual.__init__(self, **kwds)
+        # Create a program
+        self._program = ModularProgram(vertex_template, fragment_template)
+
+        # Define buffers
+        self._vertices = VertexBuffer(np.zeros((0, 3), dtype=np.float32))
+        self._normals = None
+        self._faces = IndexBuffer()
+        self._colors = VertexBuffer(np.zeros((0, 4), dtype=np.float32))
+        self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32))
+
+        # Whether to use _faces index
+        self._indexed = None
+
+        # Uniform color
+        self._color = Color(color).rgba
+
+        # primtive mode
+        self._mode = mode
+
+        # varyings
+        self._color_var = Varying('v_color', dtype='vec4')
+        self._normal_var = Varying('v_normal', dtype='vec3')
+
+        # Function for computing phong shading
+        self._phong = None
+
+        # Init
+        self.shading = shading
+        # Note we do not call subclass set_data -- often the signatures
+        # do no match.
+        Mesh.set_data(self, vertices=vertices, faces=faces,
+                      vertex_colors=vertex_colors,
+                      face_colors=face_colors, meshdata=meshdata)
+
+    def set_data(self, vertices=None, faces=None, vertex_colors=None,
+                 face_colors=None, meshdata=None, color=None):
+        if meshdata is not None:
+            self._meshdata = meshdata
+        else:
+            self._meshdata = MeshData(vertices=vertices, faces=faces,
+                                      vertex_colors=vertex_colors,
+                                      face_colors=face_colors)
+        if color is not None:
+            self._color = Color(color).rgba
+        self.mesh_data_changed()
+
+    def mesh_data_changed(self):
+        self._data_changed = True
+        self.update()
+
+    def _update_data(self):
+        md = self._meshdata
+
+        # Update vertex/index buffers
+        if self.shading == 'smooth' and not md.has_face_indexed_data():
+            v = md.vertices()
+            self._vertices.set_data(v, convert=True)
+            self._normals.set_data(md.vertex_normals(), convert=True)
+            self._faces.set_data(md.faces(), convert=True)
+            self._indexed = True
+            if md.has_vertex_color():
+                self._colors.set_data(md.vertex_colors(), convert=True)
+            elif md.has_face_color():
+                self._colors.set_data(md.face_colors(), convert=True)
+            else:
+                self._colors.set_data(np.zeros((0, 4), dtype=np.float32))
+        else:
+            v = md.vertices(indexed='faces')
+            self._vertices.set_data(v, convert=True)
+            if self.shading == 'smooth':
+                normals = md.vertex_normals(indexed='faces')
+                self._normals.set_data(normals, convert=True)
+            elif self.shading == 'flat':
+                normals = md.face_normals(indexed='faces')
+                self._normals.set_data(normals, convert=True)
+            else:
+                self._normals.set_data(np.zeros((0, 3), dtype=np.float32))
+            self._indexed = False
+            if md.has_vertex_color():
+                self._colors.set_data(md.vertex_colors(indexed='faces'), 
+                                      convert=True)
+            elif md.has_face_color():
+                self._colors.set_data(md.face_colors(indexed='faces'), 
+                                      convert=True)
+            else:
+                self._colors.set_data(np.zeros((0, 4), dtype=np.float32))
+
+        # Position input handling
+        if v.shape[-1] == 2:
+            self._program.vert['position'] = vec2to4(self._vertices)
+        elif v.shape[-1] == 3:
+            self._program.vert['position'] = vec3to4(self._vertices)
+        else:
+            raise TypeError("Vertex data must have shape (...,2) or (...,3).")
+
+        # Color input handling
+        colors = self._colors if self._colors.size > 0 else self._color
+        self._program.vert[self._color_var] = colors
+
+        # Shading
+        if self.shading is None:
+            self._program.frag['color'] = self._color_var
+            self._phong = None
+        else:
+            self._phong = Function(phong_template)
+
+            # Normal data comes via vertex shader
+            if self._normals.size > 0:
+                normals = self._normals
+            else:
+                normals = (1., 0., 0.)
+
+            self._program.vert[self._normal_var] = normals
+            self._phong['normal'] = self._normal_var
+
+            # Additional phong proprties
+            self._phong['light_dir'] = (1.0, 1.0, 5.0)
+            self._phong['light_color'] = (1.0, 1.0, 1.0, 1.0)
+            self._phong['ambient'] = (0.3, 0.3, 0.3, 1.0)
+
+            self._program.frag['color'] = self._phong(self._color_var)
+
+    @property
+    def shading(self):
+        """ The shading method used.
+        """
+        return self._shading
+
+    @shading.setter
+    def shading(self, value):
+        assert value in (None, 'flat', 'smooth')
+        self._shading = value
+
+    def draw(self, event):
+        set_state('translucent', depth_test=True, cull_face='front_and_back')
+        if self._data_changed:
+            self._update_data()
+
+        self._program.vert['transform'] = event.render_transform.shader_map()
+        if self._phong is not None:
+            self._phong['transform'] = event.document_transform().shader_map()
+
+        # Draw
+        if self._indexed:
+            self._program.draw(self._mode, self._faces)
+        else:
+            self._program.draw(self._mode)
diff --git a/vispy/scene/visuals/modular_line.py b/vispy/scene/visuals/modular_line.py
new file mode 100644
index 0000000..cb65ecc
--- /dev/null
+++ b/vispy/scene/visuals/modular_line.py
@@ -0,0 +1,28 @@
+from .modular_visual import ModularVisual
+
+
+class ModularLine(ModularVisual):
+    """
+    Displays multiple line segments.
+    """
+    def __init__(self, parent=None, pos=None, color=None, z=0.0,
+                 mode='line_strip', **kwds):
+        super(ModularLine, self).__init__(parent=parent, **kwds)
+
+        glopts = kwds.pop('gl_options', 'translucent')
+        self.set_gl_options(glopts)
+        glopts = kwds.pop('gl_options', 'translucent')
+        self.set_gl_options(glopts)
+        if mode in ('lines', 'line_strip'):
+            self._primitive = mode
+        else:
+            raise ValueError("Invalid line mode '%s'; must be 'lines' or "
+                             "'line-strip'.")
+
+        if pos is not None or color is not None or z is not None:
+            self.set_data(pos=pos, color=color, z=z)
+
+    def set_data(self, pos=None, **kwds):
+        kwds['index'] = kwds.pop('edges', kwds.get('index', None))
+        kwds.pop('width', 1)  # todo: do something with width
+        super(ModularLine, self).set_data(pos, **kwds)
diff --git a/vispy/scene/visuals/modular_mesh.py b/vispy/scene/visuals/modular_mesh.py
new file mode 100644
index 0000000..1a1ef81
--- /dev/null
+++ b/vispy/scene/visuals/modular_mesh.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+from .modular_visual import ModularVisual
+
+
+class ModularMesh(ModularVisual):
+    """
+    Displays a 3D triangle mesh.
+    """
+    def __init__(self, gl_options='translucent', faces=None, index=None, 
+                 pos=None, z=0.0, color=None, **kwargs):
+        super(ModularMesh, self).__init__(**kwargs)
+        self.set_gl_options(gl_options)
+        self.set_data(faces=faces, index=index, pos=pos, z=z, color=color)
+
+    def set_data(self, **kwds):
+        kwds['index'] = kwds.pop('faces', kwds.get('index', None))
+        super(ModularMesh, self).set_data(**kwds)
diff --git a/vispy/scene/visuals/modular_point.py b/vispy/scene/visuals/modular_point.py
new file mode 100644
index 0000000..36b2d3d
--- /dev/null
+++ b/vispy/scene/visuals/modular_point.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from ... import gloo
+from .modular_visual import ModularVisual
+from ..shaders import Function
+
+
+class ModularPoint(ModularVisual):
+    """
+    Displays multiple point sprites.
+    """
+    def __init__(self, pos=None, color=None, **kwargs):
+        super(ModularPoint, self).__init__(**kwargs)
+
+        glopts = kwargs.pop('gl_options', 'translucent')
+        self.set_gl_options(glopts)
+
+        if pos is not None or color is not None:
+            self.set_data(pos=pos, color=color)
+
+        # TODO: turn this into a proper component.
+        code = """
+        void set_point_size() {
+            gl_PointSize = 10.0; //size;
+        }
+        """
+        self._program.vert.add_callback('vert_post_hook', Function(code))
+
+    @property
+    def primitive(self):
+        return gloo.gl.GL_POINTS
+
+    def draw(self, event):
+        # HACK: True OpenGL ES does not need to enable point sprite and does
+        # not define these two constants. Desktop OpenGL needs to enable these
+        # two modes but we do not have these two constants because our GL
+        # namespace pretends to be ES.
+        GL_VERTEX_PROGRAM_POINT_SIZE = 34370
+        GL_POINT_SPRITE = 34913
+        gloo.gl.glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)
+        gloo.gl.glEnable(GL_POINT_SPRITE)
+        super(ModularPoint, self).draw(event)
diff --git a/vispy/scene/visuals/modular_visual.py b/vispy/scene/visuals/modular_visual.py
new file mode 100644
index 0000000..3e75fc0
--- /dev/null
+++ b/vispy/scene/visuals/modular_visual.py
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division, print_function
+
+import numpy as np
+
+from ... import gloo
+from .visual import Visual
+from ..shaders import ModularProgram, Variable
+from ..components import (VisualComponent, XYPosComponent, XYZPosComponent,
+                          UniformColorComponent, VertexColorComponent)
+
+"""
+  - Should have swappable input component to allow a variety of different
+    vertex inputs:
+        2d attribute + z uniform
+        3d attribute
+        2d attribute + z uniform + index
+        3d attribute + index
+        1d attribute + x/y ranges (surface plot)
+        (and any other custom input component the user might come up with)
+
+  - Should have swappable / chainable fragment components:
+        Per-vertex normals (for smooth surfaces)
+        Per-face normals (for faceted surfaces)
+        Colors per-vertex, per-face
+        Materials - phong, etc.
+        Textures - color, bump map, spec map, etc
+        Wireframe rendering (note this might require vertex modification)
+
+  - Make base shaders look like:
+       vertex_input => vertex_adjustment, transform_to_nd, post_hook
+       color_input => color_adjustment
+
+  - For efficiency, the vertex inputs should allow both pre-index and
+    unindexed arrays. However, many fragment shaders may require pre-indexed
+    arrays. For example, drawing faceted surfaces is not possible with
+    unindexed arrays since the normal vector changes each time a vertex is
+    visited.
+        => this means that input components need a way to convert their data
+           and suggest a different input component (?)
+        => More generally, we need to be able to map out all of the available
+           pathways and choose the most efficient one based on the input
+           data format (to avoid unnecessary conversions) and the requirements
+           of individual components (including indexed/unindexed, whether
+           geometry shaders are available, ...)
+
+  - Fragment shaders that do not need normals should obviously not compute them
+
+  - Some materials require a normal vector, but there may be any number of
+    ways to generate a normal: per-vertex interpolated, per-face, bump maps,
+    etc. This means we need a way for one material to indicate that it requires
+    normals, and a way to tell the component which normal-generating component
+    it should use.
+        => Likewise with colors. In fact, normals and colors are similar enough
+           that they could probably share most of the same machinery..
+
+
+    => Color chain   \
+                      ===>  Material chain
+    => Normal chain  /
+
+    Examples:
+        Color input / filters:
+            uniform color
+            color by vertex, color by face
+            texture color
+            float texture + colormap
+            color by height
+            grid contours
+            wireframe
+
+        Normal input:
+            normal per vertex
+            normal per face
+            texture bump map
+            texture normal map
+
+        Material composition:
+            shaded / facets
+            shaded / smooth
+            phong shading
+
+"""
+            
+
+class ModularVisual(Visual):
+    """
+    Abstract modular visual. This extends Visual by implementing a system
+    of attachable components that change the input and output behaviors of
+    the visual. 
+
+    * A modular GLSL program with a standard set of vertex and
+      fragment shader hooks
+    * A mechanism for adding and removing components
+      that affect the vertex position (pos_components) and fragment
+      color (color_components)
+    * A default draw() method that:
+        * activates each of the attached components
+        * negotiates a buffer mode (pre-indexed or unindexed) supported by
+          all components
+        * Requests an index buffer from components (if needed)
+        * Instructs the program to draw using self.primitive
+    * A simple set_data() method intended to serve as an example for
+      subclasses to follow.
+
+    """
+
+    VERTEX_SHADER = """
+    void main(void) {
+        $local_pos = $local_position();
+        vec4 nd_pos = $map_local_to_nd($local_pos);
+        gl_Position = nd_pos;
+
+        $vert_post_hook();
+    }
+    """
+
+    FRAGMENT_SHADER = """
+    // Fragment shader consists of only a single hook that is usually defined
+    // by a chain of functions, each which sets or modifies the curren
+    // fragment color, or discards it.
+    void main(void) {
+        gl_FragColor = $frag_color();
+    }
+    """
+
+    def __init__(self, **kwargs):
+        Visual.__init__(self, **kwargs)
+        
+        # Dict of {'GL_FLAG': bool} and {'glFunctionName': (args)} 
+        # specifications. By default, these are enabled whenever the Visual 
+        # is drawn. This provides a simple way for the user to customize the
+        # appearance of the Visual. Example:
+        #
+        #     { 'GL_BLEND': True,
+        #       'glBlendFunc': ('GL_SRC_ALPHA', 'GL_ONE') }
+        #
+        self._gl_options = [None, {}]
+
+        self._program = ModularProgram(self.VERTEX_SHADER,
+                                       self.FRAGMENT_SHADER)
+        self._program.changed.connect(self._program_changed)
+        
+        self._program.vert['local_pos'] = Variable('local_pos', 
+                                                   vtype='', dtype='vec4')
+        
+        # Generic chains for attaching post-processing functions
+        self._program.vert.add_chain('local_position')
+        self._program.vert.add_chain('vert_post_hook')
+        self._program.frag.add_chain('frag_color')
+
+        # Components for plugging different types of position and color input.
+        self._pos_components = []
+        #self._color_component = None
+        #self.pos_component = XYZPosComponent()
+        self._color_components = []
+        #self.color_components = [UniformColorComponent()]
+
+        # Primitive, default is GL_TRIANGLES
+        self._primitive = gloo.gl.GL_TRIANGLES
+    
+    @property
+    def primitive(self):
+        """
+        The GL primitive used to draw this visual.
+        """
+        return self._primitive
+
+    @property
+    def vertex_index(self):
+        """
+        Returns the IndexBuffer (or None) that should be used when drawing
+        this Visual.
+        """
+        # TODO: What to do here? How do we decide which component should
+        # generate the index?
+        return self.pos_components[0].index
+
+    def set_data(self, pos=None, index=None, z=0.0, color=None):
+        """
+        Default set_data implementation is only used for a few visuals..
+        *pos* must be array of shape (..., 2) or (..., 3).
+        *z* is only used in the former case.
+        """
+        # select input component based on pos.shape
+        if pos is not None:
+            if pos.shape[-1] == 2:
+                comp = XYPosComponent(xy=pos.astype(np.float32), 
+                                      z=z, index=index)
+                self.pos_components = [comp]
+            elif pos.shape[-1] == 3:
+                comp = XYZPosComponent(pos=pos.astype(np.float32), index=index)
+                self.pos_components = [comp]
+            else:
+                raise Exception("Can't handle position data: %s" % pos)
+
+        if color is not None:
+            if isinstance(color, tuple):
+                self.color_components = [UniformColorComponent(color)]
+            elif isinstance(color, np.ndarray):
+                if color.ndim == 1:
+                    self.color_components = [UniformColorComponent(color)]
+                elif color.ndim > 1:
+                    self.color_components = [VertexColorComponent(color)]
+            else:
+                raise Exception("Can't handle color data: %r" % color)
+
+    def set_gl_options(self, default=-1, **kwds):
+        """
+        Set all GL options for this Visual. Most common arguments are 
+        'translucent', 'opaque', and 'additive'.
+        See gloo.set_state() for more information.
+
+        These options are invoked every time the Visual is drawn.
+        """
+        if default is not -1:
+            self._gl_options[0] = default
+        self._gl_options[1] = kwds
+
+    def update_gl_options(self, default=-1, **kwds):
+        """
+        Update GL options rather than replacing all. See set_gl_options().
+        """
+        if default is not -1:
+            self._gl_options[0] = default
+        self._gl_options.update(kwds)
+
+    def gl_options(self):
+        """
+        Return the GL options in use for this Visual.
+        See set_gl_options().
+        """
+        return self._gl_options[0], self._gl_options[1].copy()
+
+    @property
+    def pos_components(self):
+        return self._pos_components[:]
+
+    @pos_components.setter
+    def pos_components(self, comps):
+        for comp in self._pos_components:
+            try:
+                comp._detach()
+            except:
+                print(comp)
+                raise
+        self._pos_components = comps
+        for comp in self._pos_components:
+            comp._attach(self)
+        self.events.update()
+
+    @property
+    def color_components(self):
+        return self._color_components[:]
+
+    @color_components.setter
+    def color_components(self, comps):
+        for comp in self._color_components:
+            try:
+                comp._detach()
+            except:
+                print(comp)
+                raise
+        self._color_components = comps
+        for comp in self._color_components:
+            comp._attach(self)
+        self.events.update()
+
+    def update(self):
+        """
+        This method is called whenever the Visual must be redrawn.
+
+        """
+        self.events.update()
+
+# no need if we use the drawing system
+#     def on_draw(self, event):
+#         """ when we get a draw event from the scenegraph
+#         """
+#         self._visual.transform = event.viewport_transform
+#         self.draw()
+
+    def draw(self, event):
+        """
+        Draw this visual now.
+
+        The default implementation configures GL flags according to the
+        contents of self._gl_options
+
+        """
+        self._activate_gl_options()
+        mode = self._draw_mode()
+        self._activate_components(mode, event)
+        self._program.draw(self.primitive, self.vertex_index)
+
+    # todo: should this be called "buffer_mode" ?
+    def _draw_mode(self):
+        """
+        Return the mode that should be used to draw this visual
+        (DRAW_PRE_INDEXED or DRAW_UNINDEXED)
+        """
+        modes = set([VisualComponent.DRAW_PRE_INDEXED,
+                     VisualComponent.DRAW_UNINDEXED])
+        for comp in (self._color_components + self.pos_components):
+            modes &= comp.supported_draw_modes
+
+        if len(modes) == 0:
+            for c in self._color_components:
+                print(c, c.supported_draw_modes)
+            raise Exception("Visual cannot draw--no mutually supported "
+                            "draw modes between components.")
+
+        #TODO: pick most efficient draw mode!
+        return list(modes)[0]
+
+    def _activate_gl_options(self):
+        gloo.set_state(self._gl_options[0], **self._gl_options[1])
+
+    def _activate_components(self, mode, event):
+        """
+        This is called immediately before drawing to inform all components
+        that a draw is about to occur and to let them assign program
+        variables.
+        """
+        if len(self._pos_components) == 0:
+            raise Exception("Cannot draw visual %s; no position components"
+                            % self)
+        if len(self._color_components) == 0:
+            raise Exception("Cannot draw visual %s; no color components"
+                            % self)
+        comps = self._pos_components + self._color_components
+        all_comps = set(comps)
+        while len(comps) > 0:
+            comp = comps.pop(0)
+            comps.extend(comp._deps)
+            all_comps |= set(comp._deps)
+
+        self._activate_transform(event)
+        
+        for comp in all_comps:
+            comp.activate(self._program, mode)
+
+    def _activate_transform(self, event):
+        # TODO: this must be optimized.
+        # Allow using as plain visual or in a scenegraph
+        t = event.render_transform.shader_map()
+        self._program.vert['map_local_to_nd'] = t
+
+    def _program_changed(self, event):
+        self.update()
diff --git a/vispy/scene/visuals/polygon.py b/vispy/scene/visuals/polygon.py
new file mode 100644
index 0000000..969b6ef
--- /dev/null
+++ b/vispy/scene/visuals/polygon.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+
+"""
+Simple polygon visual based on MeshVisual and LineVisual
+"""
+
+from __future__ import division
+
+import numpy as np
+
+from ... import gloo
+from .visual import Visual
+from .mesh import Mesh
+from .line import Line
+from ...color import Color
+from ...geometry import PolygonData
+
+
+class Polygon(Visual):
+    """
+    Displays a 2D polygon
+
+    Parameters
+    ----------
+    pos : array
+        Set of vertices defining the polygon
+    color : str | tuple | list of colors
+        Fill color of the polygon
+    border_color : str | tuple | list of colors
+        Border color of the polygon
+    """
+    def __init__(self, pos=None, color='black',
+                 border_color=None, **kwds):
+        super(Polygon, self).__init__(**kwds)
+
+        self.mesh = None
+        self.border = None
+        self._pos = pos
+        self._color = Color(color)
+        self._border_color = Color(border_color)
+        self._update()
+        #glopts = kwds.pop('gl_options', 'translucent')
+        #self.set_gl_options(glopts)
+
+    @property
+    def transform(self):
+        """ The transform that maps the local coordinate frame to the
+        coordinate frame of the parent.
+        """
+        return Visual.transform.fget(self)
+
+    @transform.setter
+    def transform(self, tr):
+        Visual.transform.fset(self, tr)
+        if self.mesh is not None:
+            self.mesh.transform = tr
+        if self.border is not None:
+            self.border.transform = tr
+
+    @property
+    def pos(self):
+        """ The vertex position of the polygon.
+        """
+        return self._pos
+
+    @pos.setter
+    def pos(self, pos):
+        self._pos = pos
+        self._update()
+
+    @property
+    def color(self):
+        """ The color of the polygon.
+        """
+        return self._color
+
+    @color.setter
+    def color(self, color):
+        self._color = Color(color)
+        self._update()
+
+    @property
+    def border_color(self):
+        """ The border color of the polygon.
+        """
+        return self._border_color
+
+    @border_color.setter
+    def border_color(self, border_color):
+        self._border_color = Color(border_color)
+        self._update()
+
+    def _update(self):
+        self.data = PolygonData(vertices=np.array(self._pos, dtype=np.float32))
+        if self._pos is not None:
+            pts, tris = self.data.triangulate()
+            self.mesh = Mesh(vertices=pts, faces=tris.astype(np.uint32),
+                             color=self._color.rgba)
+            if not self._border_color.is_blank():
+                # Close border if it is not already.
+                border_pos = self._pos
+                if np.any(border_pos[0] != border_pos[1]):
+                    border_pos = np.concatenate([border_pos, border_pos[:1]], 
+                                                axis=0)
+                self.border = Line(pos=border_pos,
+                                   color=self._border_color.rgba, 
+                                   connect='strip')
+        #self.update()
+
+    def set_gl_options(self, *args, **kwds):
+        self.mesh.set_gl_options(*args, **kwds)
+
+    def update_gl_options(self, *args, **kwds):
+        self.mesh.update_gl_options(*args, **kwds)
+
+    def draw(self, event):
+        if self.mesh is not None:
+            gloo.set_state(polygon_offset_fill=True, 
+                           cull_face='front_and_back')
+            gloo.set_polygon_offset(1, 1)
+            self.mesh.draw(event)
+        if self.border is not None:
+            self.border.draw(event)
diff --git a/vispy/scene/visuals/rectangle.py b/vispy/scene/visuals/rectangle.py
new file mode 100644
index 0000000..ab6aa05
--- /dev/null
+++ b/vispy/scene/visuals/rectangle.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+# Copradiusight (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+
+"""
+Simple ellipse visual based on PolygonVisual
+"""
+
+from __future__ import division
+
+import numpy as np
+from ...color import Color
+from .polygon import Polygon, Mesh, Line
+
+
+class Rectangle(Polygon):
+    """
+    Displays a 2D rectangle with optional rounded corners
+
+    Parameters
+    ----------
+
+    pos :  array
+        Center of the rectangle
+    height : float
+        Length of the rectangle along y-axis
+        Defaults to 1.0
+    width : float
+        Length of the rectangle along x-axis
+        Defaults to 1.0
+    radius : float | array
+        Radii of curvatures of corners in clockwise order from top-left
+        Defaults to 0.
+    """
+    def __init__(self, pos=None, color='black', border_color=None,
+                 height=1.0, width=1.0, radius=[0., 0., 0., 0.], **kwds):
+        super(Rectangle, self).__init__()
+        self._vertices = None
+        self._pos = pos
+        self._color = Color(color)
+        self._border_color = Color(border_color)
+        self._height = height
+        self._width = width
+        self.radius = radius
+        self._update()
+
+    def _generate_vertices(self, pos, radius, height, width):
+
+        half_height = self._height / 2.
+        half_width = self._width / 2.
+        hw = min(half_height, half_width)
+
+        num_segments = (radius / hw * 500.).astype(int)
+
+        bias1 = np.full(4, half_width) - radius
+        bias2 = np.full(4, half_height) - radius
+
+        corner1 = np.empty([num_segments[0]+1, 3], dtype=np.float32)
+        corner2 = np.empty([num_segments[1]+1, 3], dtype=np.float32)
+        corner3 = np.empty([num_segments[2]+1, 3], dtype=np.float32)
+        corner4 = np.empty([num_segments[3]+1, 3], dtype=np.float32)
+
+        start_angle = 0.
+        end_angle = np.pi / 2.
+
+        theta = np.linspace(end_angle, start_angle, num_segments[0]+1)
+
+        corner1[:, 0] = pos[0] - bias1[0] - radius[0] * np.sin(theta)
+        corner1[:, 1] = pos[1] - bias2[0] - radius[0] * np.cos(theta)
+        corner1[:, 2] = 0
+
+        theta = np.linspace(start_angle, end_angle, num_segments[1]+1)
+
+        corner2[:, 0] = pos[0] + bias1[1] + radius[1] * np.sin(theta)
+        corner2[:, 1] = pos[1] - bias2[1] - radius[1] * np.cos(theta)
+        corner2[:, 2] = 0
+
+        theta = np.linspace(end_angle, start_angle, num_segments[2]+1)
+
+        corner3[:, 0] = pos[0] + bias1[2] + radius[2] * np.sin(theta)
+        corner3[:, 1] = pos[1] + bias2[2] + radius[2] * np.cos(theta)
+        corner3[:, 2] = 0
+
+        theta = np.linspace(start_angle, end_angle, num_segments[3]+1)
+
+        corner4[:, 0] = pos[0] - bias1[3] - radius[3] * np.sin(theta)
+        corner4[:, 1] = pos[1] + bias2[3] + radius[3] * np.cos(theta)
+        corner4[:, 2] = 0
+
+        output = np.concatenate(([[pos[0], pos[1], 0.]],
+                                 [[pos[0] - half_width, pos[1], 0.]],
+                                 corner1,
+                                 [[pos[0], pos[1] - half_height, 0.]],
+                                 corner2,
+                                 [[pos[0] + half_width, pos[1], 0.]],
+                                 corner3,
+                                 [[pos[0], pos[1] + half_height, 0.]],
+                                 corner4,
+                                 [[pos[0] - half_width, pos[1], 0.]]))
+
+        self._vertices = np.array(output, dtype=np.float32)
+
+    @property
+    def height(self):
+        """ The height of the rectangle.
+        """
+        return self._height
+
+    @height.setter
+    def height(self, height):
+        if height <= 0.:
+            raise ValueError('Height must be positive')
+        self._height = height
+        self._update()
+
+    @property
+    def width(self):
+        """ The width of the rectangle.
+        """
+        return self._width
+
+    @width.setter
+    def width(self, width):
+        if width <= 0.:
+            raise ValueError('Width must be positive')
+        self._width = width
+        self._update()
+
+    @property
+    def radius(self):
+        """ The radius of curvature of rounded corners.
+        """
+        return self._radius
+
+    @radius.setter
+    def radius(self, radius):
+        half_height = self._height / 2.
+        half_width = self._width / 2.
+        hw = min(half_height, half_width)
+
+        if isinstance(radius, (list, tuple)):
+            if len(radius) != 4:
+                raise ValueError("radius must be float or 4 value tuple/list"
+                                 " (got %s of length %d)" % (type(radius),
+                                                             len(radius)))
+
+            if (radius > np.full(4, hw)).all():
+                raise ValueError('Radius of curvature cannot be greater than\
+                                  half of min(width, height)')
+            radius = np.array(radius, dtype=np.float32)
+
+        else:
+            if radius > hw:
+                raise ValueError('Radius of curvature cannot be greater than\
+                                  half of min(width, height)')
+            radius = np.full(4, radius)
+
+        self._radius = radius
+        self._update()
+
+    def _update(self):
+        if self._pos is not None:
+            self._generate_vertices(pos=self._pos, radius=self._radius,
+                                    height=self._height, width=self._width,
+                                    )
+            self.mesh = Mesh(vertices=self._vertices, color=self._color.rgba,
+                             mode='triangle_fan')
+            if not self._border_color.is_blank():
+                self.border = Line(pos=self._vertices[1:, ..., :2],
+                                   color=self._border_color.rgba)
+        #self.update()
diff --git a/vispy/scene/visuals/regular_polygon.py b/vispy/scene/visuals/regular_polygon.py
new file mode 100644
index 0000000..aefc40c
--- /dev/null
+++ b/vispy/scene/visuals/regular_polygon.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+
+"""
+RegularPolygon visual based on EllipseVisual
+"""
+
+from __future__ import division
+
+from ...color import Color
+from .ellipse import Ellipse, Mesh, Line
+
+
+class RegularPolygon(Ellipse):
+    """
+    Displays a regular polygon
+
+    Parameters
+    ----------
+
+    pos : array
+        Center of the regular polygon
+    radius : float
+        Radius of the regular polygon
+        Defaults to  0.1
+    sides : int
+        Number of sides of the regular polygon
+    color : str | tuple | list of colors
+        Fill color of the polygon
+    border_color : str | tuple | list of colors
+        Border color of the polygon
+    """
+    def __init__(self, pos=None, color='black', border_color=None,
+                 radius=0.1, sides=4, **kwds):
+        super(Ellipse, self).__init__()
+        self._pos = pos
+        self._color = Color(color)
+        self._border_color = Color(border_color)
+        self._radius = radius
+        self._sides = sides
+        self._update()
+
+    @property
+    def sides(self):
+        """ The number of sides in the regular polygon.
+        """
+        return self._sides
+
+    @sides.setter
+    def sides(self, sides):
+        if sides < 3:
+            raise ValueError('Polygon must have at least 3 sides, not %s'
+                             % sides)
+        self._sides = sides
+        self._update()
+
+    def _update(self):
+        if self._pos is not None:
+            self._generate_vertices(pos=self._pos, radius=self._radius,
+                                    start_angle=0.,
+                                    span_angle=360.,
+                                    num_segments=self._sides)
+            self.mesh = Mesh(vertices=self._vertices, color=self._color.rgba,
+                             mode='triangle_fan')
+            if not self._border_color.is_blank():
+                self.border = Line(pos=self._vertices[1:],
+                                   color=self._border_color.rgba)
diff --git a/vispy/scene/visuals/surface_plot.py b/vispy/scene/visuals/surface_plot.py
new file mode 100644
index 0000000..40ccc63
--- /dev/null
+++ b/vispy/scene/visuals/surface_plot.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from .mesh import Mesh
+from ...geometry import MeshData
+
+
+class SurfacePlot(Mesh):
+    """Displays a surface plot on a regular x,y grid
+
+    Parameters
+    ----------
+    x : ndarray | None
+        1D array of values specifying the x positions of vertices in the
+        grid. If None, values will be assumed to be integers.
+    y : ndarray | None
+        1D array of values specifying the x positions of vertices in the
+        grid. If None, values will be assumed to be integers.
+    z : ndarray
+        2D array of height values for each grid vertex.
+    colors : ndarray
+        (width, height, 4) array of vertex colors.
+
+    Notes
+    -----
+    All arguments are optional.
+
+    Note that if vertex positions are updated, the normal vectors for each
+    triangle must be recomputed. This is somewhat expensive if the surface
+    was initialized with smooth=False and very expensive if smooth=True.
+    For faster performance, initialize with compute_normals=False and use
+    per-vertex colors or a material that does not require normals.
+    """
+    def __init__(self, x=None, y=None, z=None, colors=None, **kwds):
+        # The x, y, z, and colors arguments are passed to set_data().
+        # All other keyword arguments are passed to Mesh.__init__().
+        self._x = None
+        self._y = None
+        self._z = None
+        self.__color = None
+        self.__vertices = None
+        self.__meshdata = MeshData()
+        Mesh.__init__(self, **kwds)
+        self.set_data(x, y, z, colors)
+
+    def set_data(self, x=None, y=None, z=None, colors=None):
+        """Update the data in this surface plot.
+
+        Parameters:
+        -----------
+        x : ndarray | None
+            1D array of values specifying the x positions of vertices in the
+            grid. If None, values will be assumed to be integers.
+        y : ndarray | None
+            1D array of values specifying the x positions of vertices in the
+            grid. If None, values will be assumed to be integers.
+        z : ndarray
+            2D array of height values for each grid vertex.
+        colors : ndarray
+            (width, height, 4) array of vertex colors.
+        """
+        if x is not None:
+            if self._x is None or len(x) != len(self._x):
+                self.__vertices = None
+            self._x = x
+
+        if y is not None:
+            if self._y is None or len(y) != len(self._y):
+                self.__vertices = None
+            self._y = y
+
+        if z is not None:
+            if self._x is not None and z.shape[0] != len(self._x):
+                raise TypeError('Z values must have shape (len(x), len(y))')
+            if self._y is not None and z.shape[1] != len(self._y):
+                raise TypeError('Z values must have shape (len(x), len(y))')
+            self._z = z
+            if (self.__vertices is not None and
+                    self._z.shape != self.__vertices.shape[:2]):
+                self.__vertices = None
+
+        if colors is not None:
+            self.__colors = colors
+            self.__meshdata.set_vertex_colors(colors)
+
+        if self._z is None:
+            return
+
+        update_mesh = False
+        new_vertices = False
+
+        ## Generate vertex and face array
+        if self.__vertices is None:
+            new_vertices = True
+            self.__vertices = np.empty((self._z.shape[0], self._z.shape[1], 3),
+                                       dtype=np.float32)
+            self.generate_faces()
+            self.__meshdata.set_faces(self.__faces)
+            update_mesh = True
+
+        ## Copy x, y, z data into vertex array
+        if new_vertices or x is not None:
+            if x is None:
+                if self._x is None:
+                    x = np.arange(self._z.shape[0])
+                else:
+                    x = self._x
+            self.__vertices[:, :, 0] = x.reshape(len(x), 1)
+            update_mesh = True
+
+        if new_vertices or y is not None:
+            if y is None:
+                if self._y is None:
+                    y = np.arange(self._z.shape[1])
+                else:
+                    y = self._y
+            self.__vertices[:, :, 1] = y.reshape(1, len(y))
+            update_mesh = True
+
+        if new_vertices or z is not None:
+            self.__vertices[..., 2] = self._z
+            update_mesh = True
+
+        ## Update MeshData
+        if update_mesh:
+            self.__meshdata.set_vertices(
+                self.__vertices.reshape(self.__vertices.shape[0] *
+                                        self.__vertices.shape[1], 3))
+            Mesh.set_data(self, meshdata=self.__meshdata)
+
+    def generate_faces(self):
+        cols = self._z.shape[1]-1
+        rows = self._z.shape[0]-1
+        faces = np.empty((cols*rows*2, 3), dtype=np.uint)
+        rowtemplate1 = (np.arange(cols).reshape(cols, 1) +
+                        np.array([[0, 1, cols+1]]))
+        rowtemplate2 = (np.arange(cols).reshape(cols, 1) +
+                        np.array([[cols+1, 1, cols+2]]))
+        for row in range(rows):
+            start = row * cols * 2
+            faces[start:start+cols] = rowtemplate1 + row * (cols+1)
+            faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1)
+        self.__faces = faces
diff --git a/vispy/scene/visuals/tests/test_ellipse.py b/vispy/scene/visuals/tests/test_ellipse.py
new file mode 100644
index 0000000..9f3269f
--- /dev/null
+++ b/vispy/scene/visuals/tests/test_ellipse.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+"""
+Tests for EllipseVisual
+All images are of size (100,100) to keep a small file size
+"""
+
+from vispy import gloo
+from vispy.scene import visuals, transforms
+from vispy.testing import (requires_application, assert_image_equal,
+                           TestingCanvas)
+
+
+ at requires_application()
+def test_circle_draw():
+    """Test drawing circles without transform using EllipseVisual"""
+    with TestingCanvas() as c:
+        ellipse = visuals.Ellipse(pos=(75, 35, 0), radius=20,
+                                  color=(1, 0, 0, 1))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/circle1.png')
+
+        gloo.clear()
+        ellipse = visuals.Ellipse(pos=(75, 35, 0), radius=20,
+                                  color=(1, 0, 0, 1),
+                                  border_color=(0, 1, 1, 1))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/circle2.png')
+
+        gloo.clear()
+        ellipse = visuals.Ellipse(pos=(75, 35, 0), radius=20,
+                                  border_color=(0, 1, 1, 1))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/circle3.png')
+
+
+ at requires_application()
+def test_ellipse_draw():
+    """Test drawing transformed ellipses using EllipseVisual"""
+    with TestingCanvas() as c:
+        ellipse = visuals.Ellipse(pos=(0., 0.), radius=(20, 15),
+                                  color=(0, 0, 1, 1))
+        ellipse.transform = transforms.STTransform(scale=(2.0, 3.0),
+                                                   translate=(50, 50))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/ellipse1.png')
+
+        gloo.clear()
+        ellipse = visuals.Ellipse(pos=(0., 0.), radius=(20, 15),
+                                  color=(0, 0, 1, 1),
+                                  border_color=(1, 0, 0, 1))
+        ellipse.transform = transforms.STTransform(scale=(2.0, 3.0),
+                                                   translate=(50, 50))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/ellipse2.png')
+
+        gloo.clear()
+        ellipse = visuals.Ellipse(pos=(0., 0.), radius=(20, 15),
+                                  border_color=(1, 0, 0, 1))
+        ellipse.transform = transforms.STTransform(scale=(2.0, 3.0),
+                                                   translate=(50, 50))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/ellipse3.png')
+
+
+ at requires_application()
+def test_arc_draw1():
+    """Test drawing arcs using EllipseVisual"""
+    with TestingCanvas() as c:
+        ellipse = visuals.Ellipse(pos=(50., 50.), radius=(20, 15),
+                                  start_angle=150., span_angle=120.,
+                                  color=(0, 0, 1, 1))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/arc1.png')
+
+        gloo.clear()
+        ellipse = visuals.Ellipse(pos=(50., 50.), radius=(20, 15),
+                                  start_angle=90., span_angle=120.,
+                                  border_color=(1, 0, 0, 1))
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/arc2.png')
+
+
+ at requires_application()
+def test_reactive_draw():
+    """Test reactive ellipse attributes"""
+    with TestingCanvas() as c:
+        ellipse = visuals.Ellipse(pos=[75, 35, 0.], radius=[20, 15],
+                                  color='yellow')
+        c.draw_visual(ellipse)
+
+        gloo.clear()
+        ellipse.pos = [70, 40, 0.]
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse1.png')
+
+        gloo.clear()
+        ellipse.radius = 25
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse2.png')
+
+        gloo.clear()
+        ellipse.color = 'red'
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse3.png')
+
+        gloo.clear()
+        ellipse.border_color = 'yellow'
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse4.png')
+
+        gloo.clear()
+        ellipse.start_angle = 140.
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse5.png')
+
+        gloo.clear()
+        ellipse.span_angle = 100.
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse6.png')
+
+        gloo.clear()
+        ellipse.num_segments = 10.
+        c.draw_visual(ellipse)
+        assert_image_equal("screenshot", 'visuals/reactive_ellipse7.png')
diff --git a/vispy/scene/visuals/tests/test_polygon.py b/vispy/scene/visuals/tests/test_polygon.py
new file mode 100644
index 0000000..d932b93
--- /dev/null
+++ b/vispy/scene/visuals/tests/test_polygon.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+"""
+Tests for PolygonVisual
+All images are of size (100,100) to keep a small file size
+"""
+
+import numpy as np
+
+from vispy import gloo
+from vispy.scene import visuals, transforms
+from vispy.testing import (requires_application, assert_image_equal,
+                           requires_scipy, TestingCanvas)
+
+
+ at requires_application()
+ at requires_scipy()
+def test_square_draw():
+    """Test drawing squares without transforms using PolygonVisual"""
+    pos = np.array([[-0.5, 0.5, 0],
+                    [0.5, 0.5, 0],
+                    [0.5, -0.5, 0],
+                    [-0.5, -0.5, 0]])
+    with TestingCanvas() as c:
+        polygon = visuals.Polygon(pos=pos, color=(1, 0, 0, 1))
+        polygon.transform = transforms.STTransform(scale=(50, 50),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/square1.png')
+
+        gloo.clear()
+        polygon = visuals.Polygon(pos=pos, color=(1, 0, 0, 1),
+                                  border_color=(1, 1, 1, 1))
+        polygon.transform = transforms.STTransform(scale=(50, 50),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/square2.png')
+
+        gloo.clear()
+        polygon = visuals.Polygon(pos=pos, border_color=(1, 1, 1, 1))
+        polygon.transform = transforms.STTransform(scale=(50, 50),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/square3.png')
+
+
+ at requires_application()
+ at requires_scipy()
+def test_rectangle_draw():
+    """Test drawing rectangles with transforms using PolygonVisual"""
+    pos = np.array([[-0.1, 0.5, 0],
+                    [0.1, 0.5, 0],
+                    [0.1, -0.5, 0],
+                    [-0.1, -0.5, 0]])
+    with TestingCanvas() as c:
+        polygon = visuals.Polygon(pos=pos, color=(1, 1, 0, 1))
+        polygon.transform = transforms.STTransform(scale=(200.0, 25),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/rectangle1.png')
+
+        gloo.clear()
+        polygon = visuals.Polygon(pos=pos, color=(1, 1, 0, 1),
+                                  border_color=(1, 0, 0, 1))
+        polygon.transform = transforms.STTransform(scale=(200.0, 25),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/rectangle2.png')
+
+        gloo.clear()
+        polygon = visuals.Polygon(pos=pos, border_color=(1, 0, 0, 1))
+        polygon.transform = transforms.STTransform(scale=(200.0, 25),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/rectangle3.png')
+
+
+ at requires_application()
+ at requires_scipy()
+def test_reactive_draw():
+    """Test reactive polygon attributes"""
+    pos = np.array([[-0.1, 0.5, 0],
+                    [0.1, 0.5, 0],
+                    [0.1, -0.5, 0],
+                    [-0.1, -0.5, 0]])
+    with TestingCanvas() as c:
+        polygon = visuals.Polygon(pos=pos, color='yellow')
+        polygon.transform = transforms.STTransform(scale=(50, 50),
+                                                   translate=(50, 50))
+        c.draw_visual(polygon)
+
+        gloo.clear()
+        polygon.pos += [0.1, -0.1, 0]
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/reactive_polygon1.png')
+
+        gloo.clear()
+        polygon.color = 'red'
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/reactive_polygon2.png')
+
+        gloo.clear()
+        polygon.border_color = 'yellow'
+        c.draw_visual(polygon)
+        assert_image_equal("screenshot", 'visuals/reactive_polygon3.png')
diff --git a/vispy/scene/visuals/tests/test_regular_polygon.py b/vispy/scene/visuals/tests/test_regular_polygon.py
new file mode 100644
index 0000000..810490f
--- /dev/null
+++ b/vispy/scene/visuals/tests/test_regular_polygon.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+"""
+Tests for RegularPolygonVisual
+All images are of size (100,100) to keep a small file size
+"""
+
+from vispy import gloo
+from vispy.scene import visuals, transforms
+from vispy.testing import (requires_application, assert_image_equal,
+                           TestingCanvas)
+
+
+ at requires_application()
+def test_regular_polygon_draw1():
+    """Test drawing regular polygons without transforms using RegularPolygonVisual"""  # noqa
+    with TestingCanvas() as c:
+        rpolygon = visuals.RegularPolygon(pos=(0., 0.), radius=0.4, sides=8,
+                                          color=(1, 0, 0, 1))
+        rpolygon.transform = transforms.STTransform(scale=(50, 50),
+                                                    translate=(50, 50))
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot", 'visuals/regular_polygon1.png')
+
+        gloo.clear()
+        rpolygon = visuals.RegularPolygon(pos=(0., 0.), radius=0.4, sides=8,
+                                          color=(1, 0, 0, 1),
+                                          border_color=(0, 1, 1, 1))
+        rpolygon.transform = transforms.STTransform(scale=(50, 50),
+                                                    translate=(50, 50))
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot", 'visuals/regular_polygon2.png')
+
+        gloo.clear()
+        rpolygon = visuals.RegularPolygon(pos=(0., 0.), radius=0.4, sides=8,
+                                          border_color=(0, 1, 1, 1))
+        rpolygon.transform = transforms.STTransform(scale=(50, 50),
+                                                    translate=(50, 50))
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot", 'visuals/regular_polygon3.png')
+
+
+ at requires_application()
+def test_regular_polygon_draw2():
+    """Test drawing transformed regular polygons using RegularPolygonVisual"""  # noqa
+    with TestingCanvas() as c:
+        rpolygon = visuals.RegularPolygon(pos=(0., 0.), radius=0.4, sides=8,
+                                          color=(0, 0, 1, 1))
+        rpolygon.transform = transforms.STTransform(scale=(75, 100),
+                                                    translate=(50, 50))
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot", 'visuals/regular_polygon4.png')
+
+        gloo.clear()
+        rpolygon = visuals.RegularPolygon(pos=(0., 0.), radius=0.4, sides=8,
+                                          color=(0, 0, 1, 1),
+                                          border_color=(1, 0, 0, 1))
+        rpolygon.transform = transforms.STTransform(scale=(75, 100),
+                                                    translate=(50, 50))
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot", 'visuals/regular_polygon5.png')
+
+        gloo.clear()
+        rpolygon = visuals.RegularPolygon(pos=(0., 0.), radius=0.4, sides=8,
+                                          border_color=(1, 0, 0, 1))
+        rpolygon.transform = transforms.STTransform(scale=(75, 100),
+                                                    translate=(50, 50))
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot", 'visuals/regular_polygon6.png')
+
+
+ at requires_application()
+def test_reactive_draw():
+    """Test reactive regular polygon attributes"""
+    with TestingCanvas() as c:
+        rpolygon = visuals.RegularPolygon(pos=[50, 50, 0.], radius=20, sides=8,
+                                          color='yellow')
+        c.draw_visual(rpolygon)
+
+        gloo.clear()
+        rpolygon.pos = [70, 40, 0.]
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot",
+                           'visuals/reactive_regular_polygon1.png')
+
+        gloo.clear()
+        rpolygon.radius = 25
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot",
+                           'visuals/reactive_regular_polygon2.png')
+
+        gloo.clear()
+        rpolygon.color = 'red'
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot",
+                           'visuals/reactive_regular_polygon3.png')
+
+        gloo.clear()
+        rpolygon.border_color = 'yellow'
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot",
+                           'visuals/reactive_regular_polygon4.png')
+
+        gloo.clear()
+        rpolygon.sides = 6
+        c.draw_visual(rpolygon)
+        assert_image_equal("screenshot",
+                           'visuals/reactive_regular_polygon5.png')
diff --git a/vispy/scene/visuals/tests/test_sdf.py b/vispy/scene/visuals/tests/test_sdf.py
new file mode 100644
index 0000000..77d5840
--- /dev/null
+++ b/vispy/scene/visuals/tests/test_sdf.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+import numpy as np
+from numpy.testing import assert_allclose
+
+from vispy.app import Canvas
+from vispy.scene.visuals.text._sdf import SDFRenderer
+from vispy import gloo
+from vispy.testing import requires_application
+
+
+ at requires_application()
+def test_text():
+    """Test basic text support"""
+    # test a simple cases
+    data = (np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
+                      [0, 0, 0, 0, 0, 0, 0, 0, 0],
+                      [0, 0, 1, 1, 1, 1, 1, 0, 0],
+                      [0, 0, 1, 1, 1, 1, 1, 0, 0],
+                      [0, 0, 1, 1, 1, 1, 1, 0, 0],
+                      [0, 0, 1, 1, 1, 1, 1, 0, 0]]) * 255).astype(np.uint8)
+    expd = (np.array([[8, 5, 4, 4, 4, 4, 4, 5, 8],
+                      [5, 2, 1, 1, 1, 1, 1, 2, 5],
+                      [4, 1, 0, 0, 0, 0, 0, 1, 4],
+                      [4, 1, 0, -1, -4, -1, 0, 1, 4],  # XXX artifact
+                      [4, 1, 0, -1, -4, -1, 0, 1, 4],
+                      [4, 1, 0, -1, -4, -1, 0, 1, 4]]))
+    expd = 0.5 - (np.sqrt(np.abs(expd)) * np.sign(expd)) / 256. * 8
+    expd = np.round(256 * expd).astype(np.int)
+
+    with Canvas(size=(100, 100)):
+        tex = gloo.Texture2D(shape=data.shape + (3,), dtype=np.ubyte,
+                             format='rgb')
+        SDFRenderer().render_to_texture(data, tex, (0, 0), data.shape[::-1])
+        gloo.set_viewport(0, 0, *data.shape[::-1])
+        gloo.util.draw_texture(tex)
+        result = gloo.util._screenshot()[:, :, 0].astype(np.int)
+        print(result)
+        print(expd)
+        assert_allclose(result, expd, atol=1)
diff --git a/vispy/scene/visuals/tests/test_text.py b/vispy/scene/visuals/tests/test_text.py
new file mode 100644
index 0000000..2d9022d
--- /dev/null
+++ b/vispy/scene/visuals/tests/test_text.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from vispy.scene.visuals import Text
+from vispy.testing import (requires_application, TestingCanvas,
+                           assert_image_equal)
+
+
+ at requires_application()
+def test_text():
+    """Test basic text support"""
+    with TestingCanvas(bgcolor='w', size=(92, 92)) as c:
+        pos = [92 // 2] * 2
+        text = Text('testing', font_size=20, color='k',
+                    pos=pos, anchor_x='center', anchor_y='baseline')
+        c.draw_visual(text)
+        # This limit seems large, but the images actually match quite well...
+        # TODO: we should probably make more "modes" for assert_image_equal
+        # at some point
+        # Test image created in Illustrator CS5, 1"x1" output @ 92 DPI
+        assert_image_equal("screenshot", 'visuals/text1.png', limit=840)
diff --git a/vispy/scene/visuals/text/__init__.py b/vispy/scene/visuals/text/__init__.py
new file mode 100644
index 0000000..2474929
--- /dev/null
+++ b/vispy/scene/visuals/text/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from .text import Text  # noqa
diff --git a/vispy/scene/visuals/text/_sdf.py b/vispy/scene/visuals/text/_sdf.py
new file mode 100644
index 0000000..e08db66
--- /dev/null
+++ b/vispy/scene/visuals/text/_sdf.py
@@ -0,0 +1,308 @@
+# -*- coding: utf-8 -*-
+"""
+Jump flooding algoritm for EDT using GLSL code:
+Author: Stefan Gustavson (stefan.gustavson at gmail.com)
+2010-08-24. This code is in the public domain.
+
+Adapted to `vispy` by Eric Larson <larson.eric.d at gmail.com>.
+"""
+
+import numpy as np
+from os import path as op
+from ....gloo import (Program, VertexShader, FragmentShader, FrameBuffer,
+                      VertexBuffer, Texture2D, set_viewport, set_state)
+
+this_dir = op.dirname(__file__)
+
+vert = """
+uniform float u_texw;
+uniform float u_texh;
+uniform float u_step;
+attribute vec2 a_position;
+attribute vec2 a_texcoord;
+varying float v_stepu;
+varying float v_stepv;
+varying vec2 v_uv;
+
+void main( void )
+{
+  v_uv = a_texcoord.xy;
+  v_stepu = u_step / u_texw; // Saves a division in the fragment shader
+  v_stepv = u_step / u_texh;
+  gl_Position = vec4(a_position.xy, 0., 1.);
+}
+"""
+
+frag_seed = """
+uniform sampler2D u_texture;
+varying vec2 v_uv;
+
+void main( void )
+{
+  float pixel = texture2D(u_texture, v_uv).r;
+  vec4 myzero = vec4(128. / 255., 128. / 255., 0., 0.);  // Zero
+  vec4 myinfinity = vec4(0., 0., 0., 0.);                // Infinity
+  // Pixels >= 0.5 are objects, others are background
+  gl_FragColor = pixel >= 0.5 ? myzero : myinfinity;
+}
+"""
+
+frag_flood = """
+uniform sampler2D u_texture;
+varying float v_stepu;
+varying float v_stepv;
+varying vec2 v_uv;
+
+vec2 remap(vec4 floatdata) {
+    vec2 scaleddata = vec2(floatdata.x * 65280. + floatdata.z * 255.,
+                           floatdata.y * 65280. + floatdata.w * 255.);
+    return scaleddata / 32768. - 1.0;
+}
+
+vec4 remap_inv(vec2 floatvec) {
+    vec2 data = (floatvec + 1.0) * 32768.;
+    float x = floor(data.x / 256.);
+    float y = floor(data.y / 256.);
+    return vec4(x, y, data.x - x * 256., data.y - y * 256.) / 255.;
+}
+
+void main( void )
+{
+  // Search for better distance vectors among 8 candidates
+  vec2 stepvec; // Relative offset to candidate being tested
+  vec2 newvec;  // Absolute position of that candidate
+  vec3 newseed; // Closest point from that candidate (.xy) and its dist (.z)
+  vec3 bestseed; // Closest seed so far
+  bestseed.xy = remap(texture2D(u_texture, v_uv).rgba);
+  bestseed.z = length(bestseed.xy);
+
+  stepvec = vec2(-v_stepu, -v_stepv);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(-v_stepu, 0.0);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(-v_stepu, v_stepv);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(0.0, -v_stepv);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(0.0, v_stepv);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(v_stepu, -v_stepv);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(v_stepu, 0.0);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  stepvec = vec2(v_stepu, v_stepv);
+  newvec = v_uv + stepvec;
+  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
+    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
+    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
+      newseed.xy = newseed.xy + stepvec;
+      newseed.z = length(newseed.xy);
+      if(newseed.z < bestseed.z) {
+        bestseed = newseed;
+      }
+    }
+  }
+
+  gl_FragColor = remap_inv(bestseed.xy);
+}
+"""
+
+frag_insert = """
+
+uniform sampler2D u_texture;
+uniform sampler2D u_pos_texture;
+uniform sampler2D u_neg_texture;
+varying float v_stepu;
+varying float v_stepv;
+varying vec2 v_uv;
+
+vec2 remap(vec4 floatdata) {
+    vec2 scaled_data = vec2(floatdata.x * 65280. + floatdata.z * 255.,
+                            floatdata.y * 65280. + floatdata.w * 255.);
+    return scaled_data / 32768. - 1.0;
+}
+
+void main( void )
+{
+    float pixel = texture2D(u_texture, v_uv).r;
+    // convert distance from normalized units -> pixels
+    vec2 rescale = vec2(v_stepu, v_stepv);
+    float shrink = 8.;
+    rescale = rescale * 256. / shrink;
+    // Without the division, 1 RGB increment = 1 px distance
+    vec2 pos_distvec = remap(texture2D(u_pos_texture, v_uv).rgba) / rescale;
+    vec2 neg_distvec = remap(texture2D(u_neg_texture, v_uv).rgba) / rescale;
+    if (pixel <= 0.5)
+        gl_FragColor = vec4(0.5 - length(pos_distvec));
+    else
+        gl_FragColor = vec4(0.5 - (shrink - 1.) / 256. + length(neg_distvec));
+}
+"""
+
+
+class SDFRenderer(object):
+    def __init__(self):
+        vert_shader = VertexShader(vert)
+        self.program_seed = Program(vert_shader, FragmentShader(frag_seed))
+        self.program_flood = Program(vert_shader, FragmentShader(frag_flood))
+        self.program_insert = Program(vert_shader, FragmentShader(frag_insert))
+        self.programs = [self.program_seed, self.program_flood,
+                         self.program_insert]
+
+        # Initialize variables
+        self.fbo_to = [FrameBuffer(), FrameBuffer(), FrameBuffer()]
+        vtype = np.dtype([('a_position', 'f4', 2), ('a_texcoord', 'f4', 2)])
+        vertices = np.zeros(4, dtype=vtype)
+        vertices['a_position'] = [[-1., -1.], [-1., 1.], [1., -1.], [1., 1.]]
+        vertices['a_texcoord'] = [[0., 0.], [0., 1.], [1., 0.], [1., 1.]]
+        vertices = VertexBuffer(vertices)
+        self.program_insert['u_step'] = 1.
+        for program in self.programs:
+            program.bind(vertices)
+
+    def render_to_texture(self, data, texture, offset, size):
+        """Render a SDF to a texture at a given offset and size
+
+        Parameters
+        ----------
+        data : array
+            Must be 2D with type np.ubyte.
+        texture : instance of Texture2D
+            The texture to render to.
+        offset : tuple of int
+            Offset (x, y) to render to inside the texture.
+        size : tuple of int
+            Size (w, h) to render inside the texture.
+        """
+        assert isinstance(texture, Texture2D)
+        set_state(blend=False, depth_test=False)
+
+        # calculate the negative half (within object)
+        orig_tex = Texture2D(255 - data, format='luminance')
+        orig_tex.wrapping = 'clamp_to_edge'
+        orig_tex.interpolation = 'nearest'
+        edf_neg_tex = self._render_edf(orig_tex)
+
+        # calculate positive half (outside object)
+        orig_tex[:, :, 0] = data
+        edf_pos_tex = self._render_edf(orig_tex)
+
+        # render final product to output texture
+        self.program_insert['u_texture'] = orig_tex
+        self.program_insert['u_pos_texture'] = edf_pos_tex
+        self.program_insert['u_neg_texture'] = edf_neg_tex
+        self.fbo_to[-1].color_buffer = texture
+        with self.fbo_to[-1]:
+            set_viewport(tuple(offset) + tuple(size))
+            self.program_insert.draw('triangle_strip')
+
+    def _render_edf(self, orig_tex):
+        """Render an EDF to a texture"""
+        # Set up the necessary textures
+        sdf_size = orig_tex.shape[:2]
+
+        comp_texs = []
+        for _ in range(2):
+            tex = Texture2D(shape=sdf_size + (4,), dtype=np.float32,
+                            format='rgba')
+            tex.interpolation = 'nearest'
+            tex.wrapping = 'clamp_to_edge'
+            comp_texs.append(tex)
+        self.fbo_to[0].color_buffer = comp_texs[0]
+        self.fbo_to[1].color_buffer = comp_texs[1]
+        for program in self.programs:
+            program['u_texh'], program['u_texw'] = sdf_size
+
+        # Do the rendering
+        last_rend = 0
+        with self.fbo_to[last_rend]:
+            set_viewport(0, 0, sdf_size[1], sdf_size[0])
+            self.program_seed['u_texture'] = orig_tex
+            self.program_seed.draw('triangle_strip')
+        stepsize = (np.array(sdf_size) // 2).max()
+        while stepsize > 0:
+            self.program_flood['u_step'] = stepsize
+            self.program_flood['u_texture'] = comp_texs[last_rend]
+            last_rend = 1 if last_rend == 0 else 0
+            with self.fbo_to[last_rend]:
+                set_viewport(0, 0, sdf_size[1], sdf_size[0])
+                self.program_flood.draw('triangle_strip')
+            stepsize //= 2
+        return comp_texs[last_rend]
diff --git a/vispy/scene/visuals/text/text.py b/vispy/scene/visuals/text/text.py
new file mode 100644
index 0000000..cc4ac5d
--- /dev/null
+++ b/vispy/scene/visuals/text/text.py
@@ -0,0 +1,475 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+##############################################################################
+# Load font into texture
+
+from __future__ import division
+
+
+import numpy as np
+from copy import deepcopy
+from os import path as op
+
+from ._sdf import SDFRenderer
+from ....gloo import (TextureAtlas, set_state, IndexBuffer, VertexBuffer,
+                      set_viewport, get_parameter)
+from ....gloo.wrappers import _check_valid
+from ....ext.six import string_types
+from ....util.fonts import _load_glyph
+from ...shaders import ModularProgram
+from ....color import Color
+from ..visual import Visual
+from ....io import _data_dir
+
+
+class TextureFont(object):
+    """Gather a set of glyphs relative to a given font name and size
+
+    Parameters
+    ----------
+    font : dict
+        Dict with entries "face", "size", "bold", "italic".
+    renderer : instance of SDFRenderer
+        SDF renderer to use.
+    """
+    def __init__(self, font, renderer):
+        self._atlas = TextureAtlas()
+        self._atlas.wrapping = 'clamp_to_edge'
+        self._kernel = np.load(op.join(_data_dir, 'spatial-filters.npy'))
+        self._renderer = renderer
+        self._font = deepcopy(font)
+        self._font['size'] = 256  # use high resolution point size for SDF
+        self._lowres_size = 64  # end at this point size for storage
+        assert (self._font['size'] % self._lowres_size) == 0
+        # spread/border at the high-res for SDF calculation; must be chosen
+        # relative to fragment_insert.glsl multiplication factor to ensure we
+        # get to zero at the edges of characters
+        self._spread = 32
+        assert self._spread % self.ratio == 0
+        self._glyphs = {}
+
+    @property
+    def ratio(self):
+        """Ratio of the initial high-res to final stored low-res glyph"""
+        return self._font['size'] // self._lowres_size
+
+    @property
+    def slop(self):
+        """Extra space along each glyph edge due to SDF borders"""
+        return self._spread // self.ratio
+
+    def __getitem__(self, char):
+        if not (isinstance(char, string_types) and len(char) == 1):
+            raise TypeError('index must be a 1-character string')
+        if char not in self._glyphs:
+            self._load_char(char)
+        return self._glyphs[char]
+
+    def _load_char(self, char):
+        """Build and store a glyph corresponding to an individual character
+
+        Parameters:
+        -----------
+        char : str
+            A single character to be represented.
+        """
+        assert isinstance(char, string_types) and len(char) == 1
+        assert char not in self._glyphs
+        # load new glyph data from font
+        _load_glyph(self._font, char, self._glyphs)
+        # put new glyph into the texture
+        glyph = self._glyphs[char]
+        bitmap = glyph['bitmap']
+
+        # convert to padded array
+        data = np.zeros((bitmap.shape[0] + 2*self._spread,
+                         bitmap.shape[1] + 2*self._spread), np.uint8)
+        data[self._spread:-self._spread, self._spread:-self._spread] = bitmap
+
+        # Store, while scaling down to proper size
+        height = data.shape[0] // self.ratio
+        width = data.shape[1] // self.ratio
+        region = self._atlas.get_free_region(width + 2, height + 2)
+        if region is None:
+            raise RuntimeError('Cannot store glyph')
+        x, y, w, h = region
+        x, y, w, h = x + 1, y + 1, w - 2, h - 2
+
+        self._renderer.render_to_texture(data, self._atlas, (x, y), (w, h))
+        u0 = x / float(self._atlas.shape[1])
+        v0 = y / float(self._atlas.shape[0])
+        u1 = (x+w) / float(self._atlas.shape[1])
+        v1 = (y+h) / float(self._atlas.shape[0])
+        texcoords = (u0, v0, u1, v1)
+        glyph.update(dict(size=(w, h), texcoords=texcoords))
+
+
+class FontManager(object):
+    """Helper to create TextureFont instances and reuse them when possible"""
+    # todo: store a font-manager on each context,
+    # or let TextureFont use a TextureAtlas for each context
+    def __init__(self):
+        self._fonts = {}
+        self._renderer = SDFRenderer()
+
+    def get_font(self, face, bold=False, italic=False):
+        """Get a font described by face and size"""
+        key = '%s-%s-%s' % (face, bold, italic)
+        if key not in self._fonts:
+            font = dict(face=face, bold=bold, italic=italic)
+            self._fonts[key] = TextureFont(font, self._renderer)
+        return self._fonts[key]
+
+
+##############################################################################
+# The visual
+
+
+def _text_to_vbo(text, font, anchor_x, anchor_y, lowres_size):
+    """Convert text characters to VBO"""
+    text_vtype = np.dtype([('a_position', 'f4', 2),
+                           ('a_texcoord', 'f4', 2)])
+    vertices = np.zeros(len(text) * 4, dtype=text_vtype)
+    prev = None
+    width = height = ascender = descender = 0
+    ratio, slop = 1. / font.ratio, font.slop
+    x_off = -slop
+    # Need to store the original viewport, because the font[char] will
+    # trigger SDF rendering, which changes our viewport
+    orig_viewport = get_parameter('viewport')
+    for ii, char in enumerate(text):
+        glyph = font[char]
+        kerning = glyph['kerning'].get(prev, 0.) * ratio
+        x0 = x_off + glyph['offset'][0] * ratio + kerning
+        y0 = glyph['offset'][1] * ratio + slop
+        x1 = x0 + glyph['size'][0]
+        y1 = y0 - glyph['size'][1]
+        u0, v0, u1, v1 = glyph['texcoords']
+        position = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]
+        texcoords = [[u0, v0], [u0, v1], [u1, v1], [u1, v0]]
+        vi = ii * 4
+        vertices['a_position'][vi:vi+4] = position
+        vertices['a_texcoord'][vi:vi+4] = texcoords
+        x_move = glyph['advance'] * ratio + kerning
+        x_off += x_move
+        ascender = max(ascender, y0 - slop)
+        descender = min(descender, y1 + slop)
+        width += x_move
+        height = max(height, glyph['size'][1] - 2*slop)
+        prev = char
+    # Also analyse chars with large ascender and descender, otherwise the
+    # vertical alignment can be very inconsistent
+    for char in 'hy':
+        glyph = font[char]
+        y0 = glyph['offset'][1] * ratio + slop
+        y1 = y0 - glyph['size'][1]
+        ascender = max(ascender, y0 - slop)
+        descender = min(descender, y1 + slop)
+        height = max(height, glyph['size'][1] - 2*slop)
+    set_viewport(*orig_viewport)
+
+    # Tight bounding box (loose would be width, font.height /.asc / .desc)
+    width -= glyph['advance'] * ratio - (glyph['size'][0] - 2*slop)
+    dx = dy = 0
+    if anchor_y == 'top':
+        dy = -ascender
+    elif anchor_y in ('center', 'middle'):
+        dy = -(height / 2 + descender)
+    elif anchor_y == 'bottom':
+        dy = -descender
+    # Already referenced to baseline
+    # elif anchor_y == 'baseline':
+    #     dy = -descender
+    if anchor_x == 'right':
+        dx = -width
+    elif anchor_x == 'center':
+        dx = -width / 2.
+    vertices['a_position'] += (dx, dy)
+    vertices['a_position'] /= lowres_size
+    return VertexBuffer(vertices)
+
+
+class Text(Visual):
+    """Visual that displays text
+
+    Parameters
+    ----------
+    text : str
+        Text to display.
+    color : instance of Color
+        Color to use.
+    bold : bool
+        Bold face.
+    italic : bool
+        Italic face.
+    face : str
+        Font face to use.
+    font_size : float
+        Point size to use.
+    pos : tuple
+        Position (x, y) of the text.
+    rotation : float
+        Rotation (in degrees) of the text clockwise.
+    anchor_x : str
+        Horizontal text anchor.
+    anchor_y : str
+        Vertical text anchor.
+    parent : instance of Entity
+        The parent of the Text visual.
+    """
+
+    VERTEX_SHADER = """
+        uniform vec2 u_pos;  // anchor position
+        uniform vec2 u_scale;  // to scale to pixel units
+        uniform float u_rotation;  // rotation in rad
+        attribute vec2 a_position; // in point units
+        attribute vec2 a_texcoord;
+
+        varying vec2 v_texcoord;
+
+        void main(void) {
+            vec4 pos = $transform(vec4(u_pos, 0.0, 1.0));
+            mat2 rot = mat2(cos(u_rotation), -sin(u_rotation),
+                            sin(u_rotation), cos(u_rotation));
+            gl_Position = pos + vec4(rot * a_position * u_scale, 0., 0.);
+            v_texcoord = a_texcoord;
+        }
+        """
+
+    FRAGMENT_SHADER = """
+        // Adapted from glumpy with permission
+        const float M_SQRT1_2 = 0.707106781186547524400844362104849039;
+        const float kernel_bias  = -0.234377;
+        const float kernel_scale = 1.241974;
+
+        uniform sampler2D u_font_atlas;
+        uniform vec2 u_font_atlas_shape;
+        uniform vec4 u_color;
+        uniform float u_npix;
+        uniform sampler2D u_kernel;
+
+        varying vec2 v_texcoord;
+        const float center = 0.5;
+
+        // CatRom interpolation code
+        vec4 filter1D_radius2(sampler2D kernel, float index, float x,
+                              vec4 c0, vec4 c1, vec4 c2, vec4 c3) {
+            float w, w_sum = 0.0;
+            vec4 r = vec4(0.0,0.0,0.0,0.0);
+            w = texture2D(kernel, vec2(0.500000+(x/2.0),index) ).r;
+            w = w*kernel_scale + kernel_bias;
+            r += c0 * w;
+            w = texture2D(kernel, vec2(0.500000-(x/2.0),index) ).r;
+            w = w*kernel_scale + kernel_bias;
+            r += c2 * w;
+            w = texture2D(kernel, vec2(0.000000+(x/2.0),index) ).r;
+            w = w*kernel_scale + kernel_bias;
+            r += c1 * w;
+            w = texture2D(kernel, vec2(1.000000-(x/2.0),index) ).r;
+            w = w*kernel_scale + kernel_bias;
+            r += c3 * w;
+            return r;
+        }
+
+        vec4 filter2D_radius2(sampler2D texture, sampler2D kernel, float index,
+                              vec2 uv, vec2 pixel) {
+            vec2 texel = uv/pixel - vec2(0.0,0.0) ;
+            vec2 f = fract(texel);
+            texel = (texel-fract(texel)+vec2(0.001,0.001))*pixel;
+            vec4 t0 = filter1D_radius2(kernel, index, f.x,
+                texture2D( texture, texel + vec2(-1,-1)*pixel),
+                texture2D( texture, texel + vec2(0,-1)*pixel),
+                texture2D( texture, texel + vec2(1,-1)*pixel),
+                texture2D( texture, texel + vec2(2,-1)*pixel));
+            vec4 t1 = filter1D_radius2(kernel, index, f.x,
+                texture2D( texture, texel + vec2(-1,0)*pixel),
+                texture2D( texture, texel + vec2(0,0)*pixel),
+                texture2D( texture, texel + vec2(1,0)*pixel),
+                texture2D( texture, texel + vec2(2,0)*pixel));
+            vec4 t2 = filter1D_radius2(kernel, index, f.x,
+                texture2D( texture, texel + vec2(-1,1)*pixel),
+                texture2D( texture, texel + vec2(0,1)*pixel),
+                texture2D( texture, texel + vec2(1,1)*pixel),
+                texture2D( texture, texel + vec2(2,1)*pixel));
+            vec4 t3 = filter1D_radius2(kernel, index, f.x,
+                texture2D( texture, texel + vec2(-1,2)*pixel),
+                texture2D( texture, texel + vec2(0,2)*pixel),
+                texture2D( texture, texel + vec2(1,2)*pixel),
+                texture2D( texture, texel + vec2(2,2)*pixel));
+            return filter1D_radius2(kernel, index, f.y, t0, t1, t2, t3);
+        }
+
+        vec4 CatRom(sampler2D texture, vec2 shape, vec2 uv) {
+            return filter2D_radius2(texture, u_kernel, 0.468750,
+                                    uv, 1.0/shape);
+        }
+
+        float contour(in float d, in float w)
+        {
+            return smoothstep(center - w, center + w, d);
+        }
+
+        float sample(sampler2D texture, vec2 uv, float w)
+        {
+            return contour(texture2D(texture, uv).r, w);
+        }
+
+        void main(void) {
+            vec4 color = u_color;
+            vec2 uv = v_texcoord.xy;
+            vec4 rgb;
+
+            // Use interpolation at high font sizes
+            if(u_npix >= 50.0)
+                rgb = CatRom(u_font_atlas, u_font_atlas_shape, uv);
+            else
+                rgb = texture2D(u_font_atlas, uv);
+            float distance = rgb.r;
+
+            // GLSL's fwidth = abs(dFdx(uv)) + abs(dFdy(uv))
+            float width = 0.5 * fwidth(distance);  // sharpens a bit
+
+            // Regular SDF
+            float alpha = contour(distance, width);
+
+            if (u_npix < 30.) {
+                // Supersample, 4 extra points
+                // Half of 1/sqrt2; you can play with this
+                float dscale = 0.5 * M_SQRT1_2;
+                vec2 duv = dscale * (dFdx(v_texcoord) + dFdy(v_texcoord));
+                vec4 box = vec4(v_texcoord-duv, v_texcoord+duv);
+                float asum = sample(u_font_atlas, box.xy, width)
+                           + sample(u_font_atlas, box.zw, width)
+                           + sample(u_font_atlas, box.xw, width)
+                           + sample(u_font_atlas, box.zy, width);
+                // weighted average, with 4 extra points having 0.5 weight
+                // each, so 1 + 0.5*4 = 3 is the divisor
+                alpha = (alpha + 0.5 * asum) / 3.0;
+            }
+
+            gl_FragColor = vec4(color.rgb, color.a * alpha);
+        }
+        """
+
+    def __init__(self, text, color='black', bold=False,
+                 italic=False, face='OpenSans', font_size=12, pos=(0, 0),
+                 rotation=0., anchor_x='center', anchor_y='center',
+                 font_manager=None, **kwargs):
+        Visual.__init__(self, **kwargs)
+        # Check input
+        assert isinstance(text, string_types)
+        valid_keys = ('top', 'center', 'middle', 'baseline', 'bottom')
+        _check_valid('anchor_y', anchor_y, valid_keys)
+        valid_keys = ('left', 'center', 'right')
+        _check_valid('anchor_x', anchor_x, valid_keys)
+        # Init font handling stuff
+        # _font_manager is a temporary solution to use global mananger
+        self._font_manager = font_manager or FontManager()
+        self._font = self._font_manager.get_font(face, bold, italic)
+        self._program = ModularProgram(self.VERTEX_SHADER,
+                                       self.FRAGMENT_SHADER)
+        self._vertices = None
+        self._anchors = (anchor_x, anchor_y)
+        # Init text properties
+        self.color = color
+        self.text = text
+        self.font_size = font_size
+        self.pos = pos
+        self.rotation = rotation
+
+    @property
+    def text(self):
+        """The text string"""
+        return self._text
+
+    @text.setter
+    def text(self, text):
+        assert isinstance(text, string_types)
+        self._text = text
+        self._vertices = None
+
+    @property
+    def font_size(self):
+        """ The font size (in points) of the text
+        """
+        return self._font_size
+
+    @font_size.setter
+    def font_size(self, size):
+        self._font_size = max(0.0, float(size))
+
+    @property
+    def color(self):
+        """ The color of the text
+        """
+        return self._color
+
+    @color.setter
+    def color(self, color):
+        self._color = Color(color)
+
+    @property
+    def rotation(self):
+        """ The rotation of the text (clockwise, in degrees)
+        """
+        return self._rotation * 180. / np.pi
+
+    @rotation.setter
+    def rotation(self, rotation):
+        self._rotation = float(rotation) * np.pi / 180.
+
+    @property
+    def pos(self):
+        """ The position of the text anchor in the local coordinate frame
+        """
+        return self._pos
+
+    @pos.setter
+    def pos(self, pos):
+        pos = [float(p) for p in pos]
+        assert len(pos) == 2
+        self._pos = tuple(pos)
+
+    def draw(self, event=None):
+        # attributes / uniforms are not available until program is built
+        if len(self.text) == 0:
+            return
+        if self._vertices is None:
+            # we delay creating vertices because it requires a context,
+            # which may or may not exist when the object is initialized
+            self._vertices = _text_to_vbo(self._text, self._font,
+                                          self._anchors[0], self._anchors[1],
+                                          self._font._lowres_size)
+            idx = (np.array([0, 1, 2, 0, 2, 3], np.uint32) +
+                   np.arange(0, 4*len(self._text), 4,
+                             dtype=np.uint32)[:, np.newaxis])
+            self._ib = IndexBuffer(idx.ravel())
+
+        if event is not None:
+            xform = event.render_transform.shader_map()
+            self._program.vert['transform'] = xform
+            px_scale = event.framebuffer_cs.transform.scale
+        else:
+            self._program.vert['transform'] = self.transform.shader_map()
+            # Rather arbitrary scale. With size=12 it takes up ~1/10 of space
+            px_scale = 0.01, 0.01
+
+        self._program.prepare()  # Force ModularProgram to set shaders
+        # todo: do some testing to verify that the scaling is correct
+        ps = (self._font_size / 72.) * 92.
+        self._program['u_npix'] = ps
+        self._program['u_font_atlas_shape'] = self._font._atlas.shape[:2]
+        self._program['u_kernel'] = self._font._kernel
+        self._program['u_scale'] = ps * px_scale[0], ps * px_scale[1]
+        self._program['u_rotation'] = self._rotation
+        self._program['u_pos'] = self._pos
+        self._program['u_color'] = self._color.rgba
+        self._program['u_font_atlas'] = self._font._atlas
+        self._program.bind(self._vertices)
+        set_state(blend=True, depth_test=False,
+                  blend_func=('src_alpha', 'one_minus_src_alpha'))
+        self._program.draw('triangles', self._ib)
diff --git a/vispy/scene/visuals/visual.py b/vispy/scene/visuals/visual.py
new file mode 100644
index 0000000..2052994
--- /dev/null
+++ b/vispy/scene/visuals/visual.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+from ...util import event
+from ..entity import Entity
+
+"""
+API Issues to work out:
+
+  * Need Visual.bounds() as described here:
+    https://github.com/vispy/vispy/issues/141
+
+"""
+
+
+class Visual(Entity):
+    """
+    Abstract class representing a drawable object.
+
+    At a minimum, Visual subclasses should extend the draw() method. 
+
+    Events:
+
+    update : Event
+        Emitted when the visual has changed and needs to be redrawn.
+    bounds_change : Event
+        Emitted when the bounds of the visual have changed.
+
+    """
+
+    def __init__(self, **kwargs):
+        Entity.__init__(self, **kwargs)
+        
+        # Add event for bounds changing
+        self.events.add(bounds_change=event.Event)
+
+    def _update(self):
+        """
+        This method is called internally whenever the Visual needs to be 
+        redrawn. By default, it emits the update event.
+        """
+        self.events.update()
+
+    def draw(self, event):
+        """
+        Draw this visual now.
+        The default implementation does nothing.
+        
+        This function is called automatically when the visual needs to be drawn
+        as part of a scenegraph, or when calling 
+        ``SceneCanvas.draw_visual(...)``. It is uncommon to call this method 
+        manually.
+        
+        The *event* argument is a SceneDrawEvent instance that provides access 
+        to transforms that the visual
+        may use to determine its relationship to the document coordinate
+        system (which provides physical measurements) and the framebuffer
+        coordinate system (which is necessary for antialiasing calculations). 
+        
+        Vertex transformation can be done either on the CPU using 
+        Transform.map(), or on the GPU using the GLSL functions generated by 
+        Transform.shader_map().
+        """
+        pass
+
+    def bounds(self, axis):
+        """
+        Return the boundaries of this visual along *axis*, which may be 0, 1, 
+        or 2. 
+        
+        This is used primarily to allow automatic ViewBox zoom/pan.
+        By default, this method returns None which indicates the object should 
+        be ignored for automatic zooming along *axis*.
+        
+        A scenegraph may also use this information to cull visuals from the
+        display list.
+        """
+        return None
diff --git a/vispy/scene/visuals/xyz_axis.py b/vispy/scene/visuals/xyz_axis.py
new file mode 100644
index 0000000..09c4c83
--- /dev/null
+++ b/vispy/scene/visuals/xyz_axis.py
@@ -0,0 +1,26 @@
+
+import numpy as np
+
+from vispy.scene.visuals import Line
+
+
+class XYZAxis(Line):
+    """
+    Simple 3D axis for indicating coordinate system orientation. Axes are
+    x=red, y=green, z=blue.
+    """
+    def __init__(self, **kwds):
+        verts = np.array([[0, 0, 0],
+                          [1, 0, 0],
+                          [0, 0, 0],
+                          [0, 1, 0],
+                          [0, 0, 0],
+                          [0, 0, 1]])
+        color = np.array([[1, 0, 0, 1],
+                          [1, 0, 0, 1],
+                          [0, 1, 0, 1],
+                          [0, 1, 0, 1],
+                          [0, 0, 1, 1],
+                          [0, 0, 1, 1]])
+        Line.__init__(self, pos=verts, color=color, connect='segments',
+                      mode='gl', **kwds)
diff --git a/vispy/scene/widgets/__init__.py b/vispy/scene/widgets/__init__.py
new file mode 100644
index 0000000..50af828
--- /dev/null
+++ b/vispy/scene/widgets/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+The vispy.scene.widgets namespace provides a range of widgets to allow
+user interaction. Widgets are rectangular Visual objects such as buttons
+and sliders.
+"""
+__all__ = ['Widget', 'ViewBox', 'Grid']
+
+from .grid import Grid  # noqa
+from .viewbox import ViewBox  # noqa
+from .widget import Widget  # noqa
diff --git a/vispy/scene/widgets/anchor.py b/vispy/scene/widgets/anchor.py
new file mode 100644
index 0000000..871b520
--- /dev/null
+++ b/vispy/scene/widgets/anchor.py
@@ -0,0 +1,25 @@
+from ..entity import Entity
+
+
+class Anchor(Entity):
+    """
+    Anchor is an entity derives parts of its transform from some other
+    corrdinate system in the scene.
+
+    The purpose is to allow children of an Anchor to draw using a position
+    (and optionally rotation) specified by one coordinate system, and scaling/
+    projection specified by another.
+
+    For example, text attached to a point in a 3D scene should be drawn in
+    a coordinate system with a simple relationship to the screen pixels, but
+    should derive its location from a position within the 3D coordinate
+    system::
+
+        root = Box()
+        view = ViewBox(parent=box)
+        plot = Line(parent=ViewBox)
+        anchor = Anchor(parent=root, anchor_to=plot, anchor_pos=(10, 0))
+        text = Text(parent=anchor,
+                    text="Always points to (10,0) relative to line.")
+
+    """
diff --git a/vispy/scene/widgets/grid.py b/vispy/scene/widgets/grid.py
new file mode 100644
index 0000000..77996fb
--- /dev/null
+++ b/vispy/scene/widgets/grid.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from .widget import Widget
+
+
+class Grid(Widget):
+    """
+    Widget that automatically sets the position and size of child Widgets to
+    proportionally divide its internal area into a grid.
+    """
+    def __init__(self, **kwds):
+        self._next_cell = [0, 0]  # row, col
+        self._cells = {}
+        self._grid_widgets = {}
+        self.spacing = 6
+        Widget.__init__(self, **kwds)
+
+    def add_widget(self, widget=None, row=None, col=None, row_span=1, 
+                   col_span=1):
+        """
+        Add a new widget to this grid.
+        """
+        if row is None:
+            row = self._next_cell[0]
+        if col is None:
+            col = self._next_cell[1]
+
+        if widget is None:
+            widget = Widget()
+
+        _row = self._cells.setdefault(row, {})
+        _row[col] = widget
+        self._grid_widgets[widget] = row, col, row_span, col_span
+        widget.add_parent(self)
+
+        self._next_cell = [row, col+col_span]
+        self._update_child_widgets()
+        return widget
+
+    def next_row(self):
+        self._next_cell = [self._next_cell[0] + 1, 0]
+
+    def _update_child_widgets(self):
+        # Resize all widgets in this grid to share space.
+        # This logic will need a lot of work..
+
+        rvals = [widget[0]+widget[2] for widget in self._grid_widgets.values()]
+        cvals = [widget[1]+widget[3] for widget in self._grid_widgets.values()]
+        if len(rvals) == 0 or len(cvals) == 0:
+            return
+
+        nrows = max(rvals)
+        ncols = max(cvals)
+
+        # determine starting/ending position of each row and column
+        s2 = self.spacing / 2.
+        rect = self.rect.padded(self.padding + self.margin - s2)
+        rows = np.linspace(rect.bottom, rect.top, nrows+1)
+        rowstart = rows[:-1] + s2
+        rowend = rows[1:] - s2
+        cols = np.linspace(rect.left, rect.right, ncols+1)
+        colstart = cols[:-1] + s2
+        colend = cols[1:] - s2
+
+        for ch in self._grid_widgets:
+            row, col, rspan, cspan = self._grid_widgets[ch]
+
+            # Translate the origin of the entity to the corner of the area
+            #ch.transform.reset()
+            #ch.transform.translate((colstart[col], rowstart[row]))
+            ch.pos = colstart[col], rowstart[row]
+
+            # ..and set the size to match.
+            w = colend[col+cspan-1]-colstart[col]
+            h = rowend[row+rspan-1]-rowstart[row]
+            ch.size = w, h
diff --git a/vispy/scene/widgets/viewbox.py b/vispy/scene/widgets/viewbox.py
new file mode 100644
index 0000000..0107bae
--- /dev/null
+++ b/vispy/scene/widgets/viewbox.py
@@ -0,0 +1,316 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from .widget import Widget
+from ..subscene import SubScene
+from ..cameras import make_camera, BaseCamera
+from ...ext.six import string_types
+from ... color import Color
+from ... import gloo
+
+
+class ViewBox(Widget):
+    """ Provides a rectangular widget to which its subscene is rendered.
+    
+    Three classes work together when using a ViewBox:
+    * The :class:`SubScene` class describes a "world" coordinate system and the
+    entities that live inside it. 
+    * ViewBox is a "window" through which we view the
+    subscene. Multiple ViewBoxes may view the same subscene.
+    * :class:`Camera` describes both the perspective from which the 
+    subscene is rendered, and the way user interaction affects that 
+    perspective. 
+    
+    In general it is only necessary to create the ViewBox; a SubScene and 
+    Camera will be generated automatically.
+    
+    Parameters
+    ----------
+    camera : None, :class:`Camera`, or str
+        The camera through which to view the SubScene. If None, then a 
+        PanZoomCamera (2D interaction) is used. If str, then the string is
+        used as the argument to :func:`make_camera`.
+    scene : None or :class:`SubScene`
+        The :class:`SubScene` instance to view. If None, a new 
+        :class:`SubScene` is created.
+    
+    All extra keyword arguments are passed to :func:`Widget.__init__`.
+    """
+    def __init__(self, camera=None, scene=None, bgcolor='black', **kwds):
+        
+        self._camera = None
+        self._bgcolor = Color(bgcolor).rgba
+        Widget.__init__(self, **kwds)
+
+        # Init preferred method to provided a pixel grid
+        self._clip_method = 'viewport'
+
+        # Each viewbox has a scene widget, which has a transform that
+        # represents the transformation imposed by camera.
+        if scene is None:
+            self._scene = SubScene()
+        elif isinstance(scene, SubScene):
+            self._scene = scene
+        else:
+            raise TypeError('Argument "scene" must be None or SubScene.')
+        self._scene.add_parent(self)
+        
+        # Camera is a helper object that handles scene transformation
+        # and user interaction.
+        if camera is None:
+            camera = 'panzoom'
+        if isinstance(camera, string_types):
+            self.camera = make_camera(camera)
+        elif isinstance(camera, BaseCamera):
+            self.camera = camera
+        else:
+            raise TypeError('Argument "camera" must be None, str, or Camera.')
+
+    @property
+    def camera(self):
+        """ The Camera in use by this ViewBox. 
+        """
+        return self._camera
+    
+    @camera.setter
+    def camera(self, cam):
+        if self._camera is not None:
+            self._camera.viewbox = None
+        self._camera = cam
+        cam.viewbox = self
+        
+    def set_camera(self, cam_type, *args, **kwds):
+        """ Create a new Camera and attach it to this ViewBox. 
+        
+        See :func:`make_camera` for arguments.
+        """
+        self.camera = make_camera(cam_type, *args, **kwds)
+        return self.camera
+
+    @property
+    def scene(self):
+        """ The root entity of the scene viewed by this ViewBox.
+        """
+        return self._scene
+
+    def add(self, entity):
+        """ Add an Entity to the scene for this ViewBox. 
+        
+        This is a convenience method equivalent to 
+        `entity.add_parent(viewbox.scene)`
+        """
+        entity.add_parent(self.scene)
+
+    @property
+    def clip_method(self):
+        """ The method used to clip the subscene to the boundaries of the 
+        viewbox.
+
+        Clipping methods are:
+        
+        * None - do not perform clipping. The default for now.
+        * 'viewport' - use glViewPort to provide a clipped sub-grid
+          onto the parent pixel grid, if possible.
+        * 'fbo' - use an FBO to draw the subscene to a texture, and
+          then render the texture in the parent scene.
+        * 'fragment' - clipping in the fragment shader TODO
+        * 'stencil' - TODO
+
+        Notes
+        -----
+        The 'viewport' method requires that the transformation (from
+        the pixel grid to this viewbox) is translate+scale only. If
+        this is not the case, the method falls back to the default.
+
+        The 'fbo' method is convenient when the result of the viewbox
+        should be reused. Otherwise the overhead can be significant and
+        the image can get slightly blurry if the transformations do
+        not match.
+
+        It is possible to have a graph with multiple stacked viewboxes
+        which each use different methods (subject to the above
+        restrictions).
+
+        """
+        return self._clip_method
+
+    @clip_method.setter
+    def clip_method(self, value):
+        valid_methods = (None, 'fragment', 'viewport', 'fbo')
+        if value not in valid_methods:
+            t = 'clip_method should be in %s' % str(valid_methods)
+            raise ValueError((t + ', not %r') % value)
+        self._clip_method = value
+
+    def draw(self, event):
+        """ Draw the viewbox border/background, and prepare to draw the 
+        subscene using the configured clipping method.
+        """
+        # todo: we could consider including some padding
+        # so that we have room *inside* the viewbox to draw ticks and stuff
+        
+        # -- Calculate resolution
+        
+        # Get current transform and calculate the 'scale' of the viewbox
+        size = self.size
+        transform = event.document_transform()
+        p0, p1 = transform.map((0, 0)), transform.map(size)
+        res = (p1 - p0)[:2]
+        res = abs(res[0]), abs(res[1])
+
+        # Set resolution (note that resolution can be non-integer)
+        self._resolution = res
+        
+        # -- Get user clipping preference
+
+        prefer = self.clip_method
+        viewport, fbo = None, None
+
+        if prefer is None:
+            pass
+        elif prefer == 'fragment':
+            raise NotImplementedError('No fragment shader clipping yet.')
+        elif prefer == 'stencil':
+            raise NotImplementedError('No stencil buffer clipping yet.')
+        elif prefer == 'viewport':
+            viewport = self._prepare_viewport(event)
+        elif prefer == 'fbo':
+            fbo = self._prepare_fbo(event)
+
+        # -- Draw
+        super(ViewBox, self).draw(event)
+
+        event.push_viewbox(self)
+
+        if fbo:
+            # Ask the canvas to activate the new FBO
+            offset = event.canvas_transform().map((0, 0))[:2]
+            size = event.canvas_transform().map(self.size)[:2] - offset
+            
+            # Draw subscene to FBO
+            event.push_fbo(fbo, offset, size)
+            event.push_entity(self.scene)
+            try:
+                gloo.clear(color=self._bgcolor, depth=True)
+                self.scene.draw(event)
+            finally:
+                event.pop_entity()
+                event.pop_fbo()
+            
+            gloo.set_state(cull_face=False)
+            self._myprogram.draw('triangle_strip')
+        elif viewport:
+            # Push viewport, draw, pop it
+            event.push_viewport(viewport)
+            event.push_entity(self.scene)
+            try:
+                self.scene.draw(event)
+            finally:
+                event.pop_entity()
+                event.pop_viewport()
+
+        else:
+            # Just draw
+            # todo: invoke fragment shader clipping
+            self.scene.draw(event)
+
+        event.pop_viewbox()
+
+    def _prepare_viewport(self, event):
+        p1 = event.map_to_framebuffer((0, 0))
+        p2 = event.map_to_framebuffer(self.size)
+        return p1[0], p1[1], p2[0]-p1[0], p2[1]-p1[1]
+
+    def _prepare_fbo(self, event):
+        """ Draw the viewbox via an FBO. This method can be applied
+        in any situation, regardless of the transformations to this
+        viewbox.
+
+        TODO:
+        Right now, this implementation create a program, texture and FBO
+        on *each* draw, because it does not work otherwise. This is probably
+        a bug in gloo that prevents using two FBO's / programs at the same
+        time.
+
+        Also, we use plain gloo and calculate the transformation
+        ourselves, assuming 2D only. Eventually we should just use the
+        transform of self. I could not get that to work, probably
+        because I do not understand the component system yet.
+        """
+
+        from vispy.gloo import gl
+        from vispy import gloo
+
+        render_vertex = """
+            attribute vec3 a_position;
+            attribute vec2 a_texcoord;
+            varying vec2 v_texcoord;
+            void main()
+            {
+                gl_Position = vec4(a_position, 1.0);
+                v_texcoord = a_texcoord;
+            }
+        """
+
+        render_fragment = """
+            uniform sampler2D u_texture;
+            varying vec2 v_texcoord;
+            void main()
+            {
+                vec4 v = texture2D(u_texture, v_texcoord);
+                gl_FragColor = vec4(v.rgb, 1.0);
+            }
+        """
+
+        # todo: don't do this on every draw
+        if True:
+            # Create program
+            self._myprogram = gloo.Program(render_vertex, render_fragment)
+            # Create texture
+            self._tex = gloo.Texture2D(shape=(10, 10, 4), dtype=np.uint8)
+            self._tex.interpolation = gl.GL_LINEAR
+            self._myprogram['u_texture'] = self._tex
+            # Create texcoords and vertices
+            # Note y-axis is inverted here because the viewbox coordinate
+            # system has origin in the upper-left, but the image is rendered
+            # to the framebuffer with origin in the lower-left.
+            texcoord = np.array([[0, 1], [1, 1], [0, 0], [1, 0]],
+                                dtype=np.float32)
+            position = np.zeros((4, 3), np.float32)
+            self._myprogram['a_texcoord'] = gloo.VertexBuffer(texcoord)
+            self._myprogram['a_position'] = self._vert = \
+                gloo.VertexBuffer(position)
+
+        # Get fbo, ensure it exists
+        fbo = getattr(self, '_fbo', None)
+        if True:  # fbo is None:
+            self._fbo = 4
+            self._fbo = fbo = gloo.FrameBuffer(self._tex,
+                                               depth=gloo.DepthBuffer((10,
+                                                                       10)))
+
+        # Set texture coords to make the texture be drawn in the right place
+        # Note that we would just use -1..1 if we would use a Visual.
+        coords = [[0, 0], [self.size[0], self.size[1]]]
+        
+        transform = event.render_transform  # * self.scene.viewbox_transform
+        coords = transform.map(coords)
+        x1, y1, z = coords[0][:3]
+        x2, y2, z = coords[1][:3]
+        vertices = np.array([[x1, y1, z], [x2, y1, z],
+                             [x1, y2, z], [x2, y2, z]],
+                            np.float32)
+        self._vert.set_data(vertices)
+
+        # Set fbo size (mind that this is set using shape!)
+        resolution = [int(i+0.5) for i in self._resolution]  # set in draw()
+        shape = resolution[1], resolution[0]
+        fbo.color_buffer.resize(shape+(4,))
+        fbo.depth_buffer.resize(shape)
+
+        return fbo
diff --git a/vispy/scene/widgets/widget.py b/vispy/scene/widgets/widget.py
new file mode 100644
index 0000000..bd925cb
--- /dev/null
+++ b/vispy/scene/widgets/widget.py
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+from __future__ import division
+
+import numpy as np
+
+from ..visuals.visual import Visual
+from ..visuals.line import Line
+from ..transforms import STTransform
+from ...util.event import Event
+from ...geometry import Rect
+from ...color import Color
+
+
+class Widget(Visual):
+    """ A widget takes up a rectangular space, intended for use in
+    a 2D pixel coordinate frame.
+
+    The widget is positioned using the transform attribute (as any
+    entity), and its extend (size) is kept as a separate property.
+    
+    Parameters
+    ----------
+    pos : (x, y)
+        A 2-element tuple to specify the top left corner of the widget.
+    size : (w, h)
+        A 2-element tuple to spicify the size of the widget.
+    border_color : color
+        The color of the border.
+    clip : bool
+        Not used :)
+    padding : int
+        The amount of padding in the widget (i.e. the space reserved between
+        the contents and the border).
+    margin : int
+        The margin to keep outside the widget's border.
+    
+    """
+
+    def __init__(self, pos=(0, 0), size=(10, 10), border_color='black',
+                 clip=False, padding=0, margin=0, **kwargs):
+        Visual.__init__(self, **kwargs)
+        
+        # todo: rename to bordercolor? -> borderwidth
+        self._border_color = tuple(Color(border_color).rgba)
+        # for drawing border
+        self._visual = Line(color=self._border_color, mode='gl')
+        # whether this widget should clip its children
+        self._clip = clip
+        # reserved space inside border
+        self._padding = padding
+        # reserved space outside border
+        self._margin = margin
+        
+        self.events.add(resize=Event)
+        self._size = 16, 16
+        self.transform = STTransform()
+        # todo: TTransform (translate only for widgets)
+
+        self._widgets = []
+        self.pos = pos
+        self.size = size
+
+    @property
+    def pos(self):
+        return tuple(self.transform.translate[:2])
+
+    @pos.setter
+    def pos(self, p):
+        assert isinstance(p, tuple)
+        assert len(p) == 2
+        if p == self.pos:
+            return
+        self.transform.translate = p[0], p[1], 0, 0
+        self._update_line()
+        #self.events.resize()
+
+    @property
+    def size(self):
+        # Note that we cannot let the size be reflected in the transform.
+        # Consider a widget of 40x40 in a pixel grid, a child widget therin
+        # with size 20x20 would get a scale of 800x800!
+        return self._size
+
+    @size.setter
+    def size(self, s):
+        assert isinstance(s, tuple)
+        assert len(s) == 2
+        if self._size == s:
+            return
+        self._size = s
+        self._update_line()
+        self.events.resize()
+        self._update_child_widgets()
+
+    @property
+    def rect(self):
+        return Rect((0, 0), self.size)
+
+    @rect.setter
+    def rect(self, r):
+        with self.events.resize.blocker():
+            self.pos = r.pos
+            self.size = r.size
+        self.update()
+        self.events.resize()
+
+    @property
+    def border_color(self):
+        """ The color of the border.
+        """
+        return self._border_color
+
+    @border_color.setter
+    def border_color(self, b):
+        self._border_color = b
+        self._visual.set_data(color=b)
+        self.update()
+
+    @property
+    def bgcolor(self):
+        """ The background color of the Widget.
+        """
+        return self._bgcolor
+
+    @bgcolor.setter
+    def bgcolor(self, value):
+        self._bgcolor = Color(value)
+        self.update()
+
+    @property
+    def margin(self):
+        return self._margin
+
+    @margin.setter
+    def margin(self, m):
+        self._margin = m
+        self._update_line()
+
+    @property
+    def padding(self):
+        return self._padding
+
+    @padding.setter
+    def padding(self, p):
+        self._padding = p
+        self._update_child_boxes()
+
+    def _update_line(self):
+        """ Update border line to match new shape """
+        if self.border_color is None:
+            return
+        m = self.margin
+        r = self.size[0] - m
+        t = self.size[1] - m
+        
+        pos = np.array([
+            [m, m],
+            [r, m],
+            [r, t],
+            [m, t],
+            [m, m]]).astype(np.float32)
+        self._visual.set_data(pos=pos)
+
+    def draw(self, event):
+        if self.border_color is not None:
+            self._visual.draw(event)
+
+    def on_resize(self, ev):
+        self._update_child_widgets()
+
+    def _update_child_widgets(self):
+        # Set the position and size of child boxes (only those added
+        # using add_widget)
+        for ch in self._widgets:
+            ch.rect = self.rect.padded(self.padding + self.margin)
+
+    def add_widget(self, widget):
+        """
+        Add a Widget as a managed child of this Widget. The child will be
+        automatically positioned and sized to fill the entire space inside
+        this Widget (unless _update_child_widgets is redefined).
+        """
+        self._widgets.append(widget)
+        widget.parent = self
+        self._update_child_widgets()
+        return widget
+
+    def add_grid(self, *args, **kwds):
+        """
+        Create a new Grid and add it as a child widget.
+
+        All arguments are given to add_widget().
+        """
+        from .grid import Grid
+        grid = Grid()
+        return self.add_widget(grid, *args, **kwds)
+
+    def add_view(self, *args, **kwds):
+        """
+        Create a new ViewBox and add it as a child widget.
+
+        All arguments are given to add_widget().
+        """
+        from .viewbox import ViewBox
+        view = ViewBox()
+        return self.add_widget(view, *args, **kwds)
+
+    def remove_widget(self, widget):
+        self._widgets.remove(widget)
+        widget.remove_parent(self)
+        self._update_child_widgets()
diff --git a/vispy/shaders/shaders.py b/vispy/shaders/shaders.py
deleted file mode 100644
index b165285..0000000
--- a/vispy/shaders/shaders.py
+++ /dev/null
@@ -1,393 +0,0 @@
-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
deleted file mode 100644
index 39dea11..0000000
--- a/vispy/shaders/transforms.py
+++ /dev/null
@@ -1,334 +0,0 @@
-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/testing/__init__.py b/vispy/testing/__init__.py
new file mode 100644
index 0000000..8fc460d
--- /dev/null
+++ b/vispy/testing/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from ._testing import (SkipTest, requires_application, requires_img_lib,  # noqa
+                      assert_is, assert_in, assert_not_in, has_backend,  # noqa
+                      glut_skip, requires_pyopengl, requires_scipy,  # noqa
+                      has_matplotlib, assert_image_equal,  # noqa
+                      save_testing_image, TestingCanvas, has_pyopengl)  # noqa
+from ._runners import _tester  # noqa
diff --git a/vispy/testing/_coverage.py b/vispy/testing/_coverage.py
new file mode 100644
index 0000000..3e1d20c
--- /dev/null
+++ b/vispy/testing/_coverage.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+# Code inspired by original nose plugin:
+# https://nose.readthedocs.org/en/latest/plugins/cover.html
+
+from nose.plugins.base import Plugin
+
+
+class MutedCoverage(Plugin):
+    """Make a silent coverage report using Ned Batchelder's coverage module."""
+
+    def configure(self, options, conf):
+        Plugin.configure(self, options, conf)
+        self.enabled = True
+        try:
+            from coverage import coverage
+        except ImportError:
+            self.enabled = False
+            self.cov = None
+            print('Module "coverage" not installed, code coverage will not '
+                  'be available')
+        else:
+            self.enabled = True
+            self.cov = coverage(auto_data=False, branch=True, data_suffix=None,
+                                source=['vispy'])
+
+    def begin(self):
+        self.cov.load()
+        self.cov.start()
+
+    def report(self, stream):
+        self.cov.stop()
+        self.cov.combine()
+        self.cov.save()
diff --git a/vispy/testing/_runners.py b/vispy/testing/_runners.py
new file mode 100644
index 0000000..2827046
--- /dev/null
+++ b/vispy/testing/_runners.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""Test running functions"""
+
+from __future__ import print_function
+
+import sys
+import os
+from os import path as op
+from subprocess import Popen
+from copy import deepcopy
+from functools import partial
+
+from ..util import use_log_level
+from ..util.ptime import time
+from ._testing import SkipTest, has_backend
+
+
+def _get_root_dir():
+    root_dir = os.getcwd()
+    if (op.isfile(op.join(root_dir, 'setup.py')) and
+            op.isdir(op.join(root_dir, 'vispy'))):
+        dev = True
+    else:
+        root_dir = op.abspath(op.join(op.dirname(__file__), '..', '..'))
+        dev = True if op.isfile(op.join(root_dir, 'setup.py')) else False
+    return root_dir, dev
+
+
+def _nose(mode, verbosity, coverage, extra_args):
+    """Run nosetests using a particular mode"""
+    cwd = os.getcwd()  # this must be done before nose import
+    try:
+        import nose  # noqa, analysis:ignore
+    except ImportError:
+        print('Skipping nosetests, nose not installed')
+        raise SkipTest()
+    extra = ('-' * 70)
+    if mode == 'nobackend':
+        print(extra + '\nRunning tests with no backend')
+        attrs = '-a !vispy_app_test '
+        app_import = ''
+    else:
+        with use_log_level('warning', print_msg=False):
+            has, why_not = has_backend(mode, out=['why_not'])
+        if has:
+            print('%s\nRunning tests with %s backend' % (extra, mode))
+            attrs = '-a vispy_app_test '
+        else:
+            msg = ('Skipping tests for backend %s, not found (%s)'
+                   % (mode, why_not))
+            print(extra + '\n' + msg + '\n' + extra + '\n')  # last \n nicer
+            raise SkipTest(msg)
+        app_import = '\nfrom vispy import use\nuse(app="%s")\n' % mode
+    sys.stdout.flush()
+    # we might as well always use coverage, since we manually disable printing!
+    # here we actually read in the Python code to avoid importing it from
+    # from vispy.testing._coverage, since doing so breaks some path stuff later
+    muted_file = op.join(op.dirname(__file__), '_coverage.py')
+    with open(muted_file, 'r') as fid:
+        imps = fid.read()
+    cv = ', addplugins=[MutedCoverage()]'
+    # if not coverage:
+    #    imps = ''
+    #    cv = ''
+    arg = (' ' + ('--verbosity=%s ' % verbosity) + attrs +
+           ' '.join(str(e) for e in extra_args))
+    # make a call to "python" so that it inherits whatever the system
+    # thinks is "python" (e.g., virtualenvs)
+    cmd = [sys.executable, '-c',
+           '%s%simport nose; nose.main(argv="%s".split(" ")%s)'
+           % (imps, app_import, arg, cv)]
+    env = deepcopy(os.environ)
+    env.update(dict(_VISPY_TESTING_TYPE=mode))
+    p = Popen(cmd, cwd=cwd, env=env)
+    stdout, stderr = p.communicate()
+    if(p.returncode):
+        raise RuntimeError('Nose failure (%s):\n%s' % (p.returncode, stderr))
+
+
+def _flake():
+    """Test flake8"""
+    orig_dir = os.getcwd()
+    root_dir, dev = _get_root_dir()
+    os.chdir(root_dir)
+    if dev:
+        sys.argv[1:] = ['vispy', 'examples', 'make']
+    else:
+        sys.argv[1:] = ['vispy']
+    sys.argv.append('--ignore=E226,E241,E265,W291,W293')
+    sys.argv.append('--exclude=six.py,py24_ordereddict.py,glfw.py,'
+                    '_proxy.py,_angle.py,_desktop.py,_pyopengl.py,'
+                    '_constants.py,png.py')
+    try:
+        from flake8.main import main
+    except ImportError:
+        print('Skipping flake8 test, flake8 not installed')
+    else:
+        print('Running flake8... ')  # if end='', first error gets ugly
+        sys.stdout.flush()
+        try:
+            main()
+        except SystemExit as ex:
+            if ex.code in (None, 0):
+                pass  # do not exit yet, we want to print a success msg
+            else:
+                raise RuntimeError('flake8 failed')
+    finally:
+        os.chdir(orig_dir)
+
+
+def _check_line_endings():
+    """Check all files in the repository for CR characters"""
+    if sys.platform == 'win32':
+        print('Skipping line endings check on Windows')
+        sys.stdout.flush()
+        return
+    print('Running line endings check... ')
+    sys.stdout.flush()
+    report = []
+    root_dir, dev = _get_root_dir()
+    if not dev:
+        root_dir = op.join(root_dir, 'vispy')
+    for dirpath, dirnames, filenames in os.walk(root_dir):
+        for fname in filenames:
+            if op.splitext(fname)[1] in ('.pyc', '.pyo', '.so', '.dll'):
+                continue
+            # Get filename
+            filename = op.join(dirpath, fname)
+            relfilename = op.relpath(filename, root_dir)
+            # Open and check
+            try:
+                text = open(filename, 'rb').read().decode('utf-8')
+            except UnicodeDecodeError:
+                continue  # Probably a binary file
+            crcount = text.count('\r')
+            if crcount:
+                lfcount = text.count('\n')
+                report.append('In %s found %i/%i CR/LF' %
+                              (relfilename, crcount, lfcount))
+
+    # Process result
+    if len(report) > 0:
+        raise RuntimeError('Found %s files with incorrect endings:\n%s'
+                           % (len(report), '\n'.join(report)))
+
+
+def _tester(label='full', coverage=False, verbosity=1, extra_args=()):
+    """Test vispy software. See vispy.test()
+    """
+    from vispy.app.backends import BACKEND_NAMES as backend_names
+    label = label.lower()
+    verbosity = int(verbosity)
+    cov = bool(coverage)
+    if cov and op.isfile('.coverage'):
+        os.remove('.coverage')
+    known_types = ['full', 'nose', 'lineendings', 'extra', 'flake',
+                   'nobackend'] + backend_names
+    if label not in known_types:
+        raise ValueError('label must be one of %s, or a backend name %s'
+                         % (known_types, backend_names))
+    work_dir = _get_root_dir()[0]
+    orig_dir = os.getcwd()
+    # figure out what we actually need to run
+    runs = []
+    if label in ('full', 'nose'):
+        for backend in backend_names:
+            runs.append([partial(_nose, backend, verbosity, cov, extra_args),
+                         backend])
+    elif label in backend_names:
+        runs.append([partial(_nose, label, verbosity, cov, extra_args), label])
+    if label in ('full', 'nose', 'nobackend'):
+        runs.append([partial(_nose, 'nobackend', verbosity, cov, extra_args),
+                     'nobackend'])
+    if label in ('full', 'extra', 'lineendings'):
+        runs.append([_check_line_endings, 'lineendings'])
+    if label in ('full', 'extra', 'flake'):
+        runs.append([_flake, 'flake'])
+    t0 = time()
+    fail = []
+    skip = []
+    for run in runs:
+        try:
+            os.chdir(work_dir)
+            run[0]()
+        except RuntimeError as exp:
+            print('Failed: %s' % str(exp))
+            fail += [run[1]]
+        except SkipTest:
+            skip += [run[1]]
+        except Exception as exp:
+            # this should only happen if we've screwed up the test setup
+            fail += [run[1]]
+            print('Failed strangely: %s\n' % str(exp))
+            import traceback
+            type, value, tb = sys.exc_info()
+            traceback.print_exception(type, value, tb)
+        else:
+            print('Passed\n')
+        finally:
+            sys.stdout.flush()
+            os.chdir(orig_dir)
+    dt = time() - t0
+    stat = '%s failed, %s skipped' % (fail if fail else 0, skip if skip else 0)
+    extra = 'failed' if fail else 'succeeded'
+    print('Testing %s (%s) in %0.3f seconds' % (extra, stat, dt))
+    sys.stdout.flush()
+    if len(fail) > 0:
+        raise RuntimeError('FAILURE')
diff --git a/vispy/testing/_testing.py b/vispy/testing/_testing.py
new file mode 100644
index 0000000..77c5779
--- /dev/null
+++ b/vispy/testing/_testing.py
@@ -0,0 +1,375 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from __future__ import print_function
+
+import numpy as np
+import sys
+import os
+import subprocess
+import inspect
+import base64
+try:
+    from nose.tools import nottest, assert_equal, assert_true
+except ImportError:
+    assert_equal = assert_true = None
+
+    class nottest(object):
+        def __init__(self, *args):
+            pass  # Avoid "object() takes no parameters"
+
+from distutils.version import LooseVersion
+
+from ..scene import SceneCanvas
+from ..ext.six.moves import http_client as httplib
+from ..ext.six.moves import urllib_parse as urllib
+from ..io import read_png, _make_png, _check_img_lib
+from ..util import use_log_level
+from ..util.fetching import get_testing_file
+from .. import gloo
+
+###############################################################################
+# Adapted from Python's unittest2 (which is wrapped by nose)
+# http://docs.python.org/2/license.html
+
+try:
+    from unittest.case import SkipTest
+except ImportError:
+    try:
+        from unittest2.case import SkipTest
+    except ImportError:
+        class SkipTest(Exception):
+            pass
+
+
+def run_subprocess(command):
+    """Run command using subprocess.Popen
+
+    Run command and wait for command to complete. If the return code was zero
+    then return, otherwise raise CalledProcessError.
+    By default, this will also add stdout= and stderr=subproces.PIPE
+    to the call to Popen to suppress printing to the terminal.
+
+    Parameters
+    ----------
+    command : list of str
+        Command to run as subprocess (see subprocess.Popen documentation).
+
+    Returns
+    -------
+    stdout : str
+        Stdout returned by the process.
+    stderr : str
+        Stderr returned by the process.
+    """
+    # code adapted with permission from mne-python
+    kwargs = dict(stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+
+    p = subprocess.Popen(command, **kwargs)
+    stdout_, stderr = p.communicate()
+
+    output = (stdout_.decode('ascii'), stderr.decode('ascii'))
+    if p.returncode:
+        print(stdout_)
+        print(stderr)
+        err_fun = subprocess.CalledProcessError.__init__
+        if 'output' in inspect.getargspec(err_fun).args:
+            raise subprocess.CalledProcessError(p.returncode, command, output)
+        else:
+            raise subprocess.CalledProcessError(p.returncode, command)
+
+    return output
+
+
+def _safe_rep(obj, short=False):
+    """Helper for assert_* ports"""
+    try:
+        result = repr(obj)
+    except Exception:
+        result = object.__repr__(obj)
+    if not short or len(result) < 80:
+        return result
+    return result[:80] + ' [truncated]...'
+
+
+def _safe_str(obj):
+    """Helper for assert_* ports"""
+    try:
+        return str(obj)
+    except Exception:
+        return object.__str__(obj)
+
+
+def _format_msg(msg, std_msg):
+    """Helper for assert_* ports"""
+    if msg is None:
+        msg = std_msg
+    else:
+        try:
+            msg = '%s : %s' % (std_msg, msg)
+        except UnicodeDecodeError:
+            msg = '%s : %s' % (_safe_str(std_msg), _safe_str(msg))
+    return msg
+
+
+def assert_in(member, container, msg=None):
+    """Backport for old nose.tools"""
+    if member in container:
+        return
+    std_msg = '%s not found in %s' % (_safe_rep(member), _safe_rep(container))
+    msg = _format_msg(msg, std_msg)
+    raise AssertionError(msg)
+
+
+def assert_not_in(member, container, msg=None):
+    """Backport for old nose.tools"""
+    if member not in container:
+        return
+    std_msg = '%s found in %s' % (_safe_rep(member), _safe_rep(container))
+    msg = _format_msg(msg, std_msg)
+    raise AssertionError(msg)
+
+
+def assert_is(expr1, expr2, msg=None):
+    """Backport for old nose.tools"""
+    if expr1 is not expr2:
+        std_msg = '%s is not %s' % (_safe_rep(expr1), _safe_rep(expr2))
+        raise AssertionError(_format_msg(msg, std_msg))
+
+
+###############################################################################
+# GL stuff
+
+def has_pyopengl():
+    try:
+        from OpenGL import GL  # noqa, analysis:ignore
+    except Exception:
+        return False
+    else:
+        return True
+
+
+def requires_pyopengl():
+    return np.testing.dec.skipif(not has_pyopengl(), 'Requires PyOpenGL')
+
+
+###############################################################################
+# App stuff
+
+def has_backend(backend, has=(), capable=(), out=()):
+    from ..app.backends import BACKENDMAP
+    using = os.getenv('_VISPY_TESTING_BACKEND', None)
+    if using is not None and using != backend:
+        # e.g., we are on  a 'pyglet' run but the test requires PyQt4
+        ret = (False,) if len(out) > 0 else False
+        for o in out:
+            ret += (None,)
+        return ret
+    # let's follow the standard code path
+    module_name = BACKENDMAP[backend.lower()][1]
+    with use_log_level('warning', print_msg=False):
+        mod = __import__('app.backends.%s' % module_name, globals(), level=2)
+    mod = getattr(mod.backends, module_name)
+    good = mod.testable
+    for h in has:
+        good = (good and getattr(mod, 'has_%s' % h))
+    for cap in capable:
+        good = (good and mod.capability[cap])
+    ret = (good,) if len(out) > 0 else good
+    for o in out:
+        ret += (getattr(mod, o),)
+    return ret
+
+
+def requires_application(backend=None, has=(), capable=()):
+    """Decorator for tests that require an application"""
+    from ..app.backends import BACKEND_NAMES
+    # avoid importing other backends if we don't need to
+    if backend is None:
+        good = False
+        for backend in BACKEND_NAMES:
+            if has_backend(backend, has=has, capable=capable):
+                good = True
+                break
+        msg = 'Requires application backend'
+    else:
+        good, why = has_backend(backend, has=has, capable=capable,
+                                out=['why_not'])
+        msg = 'Requires %s: %s' % (backend, why)
+
+    # Actually construct the decorator
+    def skip_decorator(f):
+        import nose
+        f.vispy_app_test = True  # set attribute for easy run or not
+
+        def skipper(*args, **kwargs):
+            if not good:
+                raise SkipTest("Skipping test: %s: %s" % (f.__name__, msg))
+            else:
+                return f(*args, **kwargs)
+        return nose.tools.make_decorator(f)(skipper)
+    return skip_decorator
+
+
+def glut_skip():
+    """Helper to skip a test if GLUT is the current backend"""
+    # this is basically a knownfail tool for glut
+    from ..app import use_app
+    app = use_app()
+    if app.backend_name.lower() == 'glut':
+        raise SkipTest('GLUT unstable')
+    return  # otherwise it's fine
+
+
+def requires_img_lib():
+    """Decorator for tests that require an image library"""
+    if sys.platform.startswith('win'):
+        has_img_lib = False  # PIL breaks tests on windows (!)
+    else:
+        has_img_lib = not all(c is None for c in _check_img_lib())
+    return np.testing.dec.skipif(not has_img_lib, 'imageio or PIL required')
+
+
+def has_matplotlib(version='1.2'):
+    """Determine if mpl is a usable version"""
+    try:
+        import matplotlib
+    except Exception:
+        has_mpl = False
+    else:
+        if LooseVersion(matplotlib.__version__) >= LooseVersion(version):
+            has_mpl = True
+        else:
+            has_mpl = False
+    return has_mpl
+
+
+###############################################################################
+# Visuals stuff
+
+def _has_scipy(min_version):
+    try:
+        assert isinstance(min_version, str)
+        import scipy  # noqa, analysis:ignore
+        from distutils.version import LooseVersion
+        this_version = LooseVersion(scipy.__version__)
+        if this_version < min_version:
+            return False
+    except Exception:
+        return False
+    else:
+        return True
+
+
+def requires_scipy(min_version='0.13'):
+    return np.testing.dec.skipif(not _has_scipy(min_version),
+                                 'Requires Scipy version >= %s' % min_version)
+
+
+def _save_failed_test(data, expect, filename):
+    commit, error = run_subprocess(['git', 'rev-parse',  'HEAD'])
+    name = filename.split('/')
+    name.insert(-1, commit.strip())
+    filename = '/'.join(name)
+    host = 'data.vispy.org'
+
+    # concatenate data, expect, and diff into a single image
+    ds = data.shape
+    es = expect.shape
+    if ds == es:
+        shape = (ds[0], ds[1] * 3 + 2, 4)
+        img = np.empty(shape, dtype=np.ubyte)
+        img[:] = 255
+        img[:, :ds[1], :ds[2]] = data
+        img[:, ds[1]+1:ds[1]*2+1, :ds[2]] = expect
+        img[:, ds[1]*2 + 2:, :ds[2]] = np.abs(data.astype(int) -
+                                              expect.astype(int))
+    else:
+        shape = (ds[0], ds[1] * 2 + 1, 4)
+        img = np.empty(shape, dtype=np.ubyte)
+        img[:] = 255
+        img[:ds[0], :ds[1], :ds[2]] = data
+        img[:es[0], ds[1]+1+es[1]:, :es[2]] = expect
+
+    png = _make_png(img)
+    conn = httplib.HTTPConnection(host)
+    req = urllib.urlencode({'name': filename,
+                            'data': base64.b64encode(png)})
+    conn.request('POST', '/upload.py', req)
+    response = conn.getresponse().read()
+    conn.close()
+    print("\nUpload to: \nhttp://%s/data/%s" % (host, filename))
+    if not response.startswith(b'OK'):
+        print("WARNING: Error uploading data to %s" % host)
+        print(response)
+
+
+def assert_image_equal(image, reference, limit=40):
+    """Downloads reference image and compares with image
+
+    Parameters
+    ----------
+    image: str, numpy.array
+        'screenshot' or image data
+    reference: str
+        'The filename on the remote ``test-data`` repository to download'
+    limit : int
+        Number of pixels that can differ in the image.
+    """
+    raise SkipTest("Image comparison disabled until polygon visual "
+                   "output is finalized.")
+    from ..gloo.util import _screenshot
+
+    if image == "screenshot":
+        image = _screenshot(alpha=False)
+    ref = read_png(get_testing_file(reference))[:, :, :3]
+
+    assert_equal(image.shape, ref.shape)
+
+    # check for minimum number of changed pixels, allowing for overall 1-pixel
+    # shift in any direcion
+    slices = [slice(0, -1), slice(0, None), slice(1, None)]
+    min_diff = np.inf
+    for i in range(3):
+        for j in range(3):
+            a = image[slices[i], slices[j]]
+            b = ref[slices[2-i], slices[2-j]]
+            diff = np.any(a != b, axis=2).sum()
+            if diff < min_diff:
+                min_diff = diff
+    try:
+        assert_true(min_diff <= limit,
+                    'min_diff (%s) > %s' % (min_diff, limit))
+    except AssertionError:
+        _save_failed_test(image, ref, reference)
+        raise
+
+
+class TestingCanvas(SceneCanvas):
+    def __init__(self, bgcolor='black', size=(100, 100)):
+        SceneCanvas.__init__(self, size=size, bgcolor=bgcolor)
+
+    def __enter__(self):
+        SceneCanvas.__enter__(self)
+        gloo.clear(color=self._bgcolor)
+        return self
+
+    def draw_visual(self, visual):
+        SceneCanvas.draw_visual(self, visual)
+        gloo.gl.glFlush()
+        gloo.gl.glFinish()
+
+
+ at nottest
+def save_testing_image(image, location):
+    from ..gloo.util import _screenshot
+    from ..util import make_png
+    if image == "screenshot":
+        image = _screenshot(alpha=False)
+    png = make_png(image)
+    f = open(location+'.png', 'wb')
+    f.write(png)
+    f.close()
diff --git a/vispy/shaders/__init__.py b/vispy/testing/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/testing/tests/__init__.py
diff --git a/vispy/testing/tests/test_testing.py b/vispy/testing/tests/test_testing.py
new file mode 100644
index 0000000..a497bbc
--- /dev/null
+++ b/vispy/testing/tests/test_testing.py
@@ -0,0 +1,12 @@
+from nose.tools import assert_raises
+from vispy.testing import assert_in, assert_not_in, assert_is
+
+
+def test_testing():
+    """Test testing ports"""
+    assert_raises(AssertionError, assert_in, 'foo', 'bar')
+    assert_in('foo', 'foobar')
+    assert_raises(AssertionError, assert_not_in, 'foo', 'foobar')
+    assert_not_in('foo', 'bar')
+    assert_raises(AssertionError, assert_is, None, 0)
+    assert_is(None, None)
diff --git a/vispy/util/__init__.py b/vispy/util/__init__.py
index a7ef253..05eb96f 100644
--- a/vispy/util/__init__.py
+++ b/vispy/util/__init__.py
@@ -1,11 +1,16 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team. All Rights Reserved.
+# Copyright (c) 2014, 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
+from .logs import logger, set_log_level, use_log_level  # noqa
+from .config import (_parse_command_line_arguments, config, sys_info,  # noqa
+                     save_config, get_config_keys, set_data_dir,  # noqa
+                     _TempDir)  # noqa
 
-def is_string(s): return isinstance(s, string_types)
+from . import fonts       # noqa
+from . import transforms  # noqa
+from .wrappers import test, use, run_subprocess  # noqa
diff --git a/vispy/util/color.py b/vispy/util/color.py
deleted file mode 100644
index 12eec53..0000000
--- a/vispy/util/color.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""
-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/config.py b/vispy/util/config.py
new file mode 100644
index 0000000..70c7124
--- /dev/null
+++ b/vispy/util/config.py
@@ -0,0 +1,376 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""Vispy configuration functions
+"""
+
+import os
+from os import path as op
+import json
+import sys
+import platform
+import getopt
+import traceback
+import tempfile
+import atexit
+from shutil import rmtree
+
+from .event import EmitterGroup, EventEmitter, Event
+from ..ext.six import string_types
+from .logs import logger, set_log_level, use_log_level
+
+
+class _TempDir(str):
+    """Class for creating and auto-destroying temp dir
+
+    This is designed to be used with testing modules.
+
+    We cannot simply use __del__() method for cleanup here because the rmtree
+    function may be cleaned up before this object, so we use the atexit module
+    instead.
+    """
+    def __new__(self):
+        new = str.__new__(self, tempfile.mkdtemp())
+        return new
+
+    def __init__(self):
+        self._path = self.__str__()
+        atexit.register(self.cleanup)
+
+    def cleanup(self):
+        rmtree(self._path, ignore_errors=True)
+
+
+###############################################################################
+# CONFIG
+
+# Adapted from pyzolib/paths.py:
+# https://bitbucket.org/pyzo/pyzolib/src/tip/paths.py
+def _get_vispy_app_dir():
+    """Helper to get the default directory for storing vispy data"""
+    # Define default user directory
+    user_dir = os.path.expanduser('~')
+
+    # Get system app data dir
+    path = None
+    if sys.platform.startswith('win'):
+        path1, path2 = os.getenv('LOCALAPPDATA'), os.getenv('APPDATA')
+        path = path1 or path2
+    elif sys.platform.startswith('darwin'):
+        path = os.path.join(user_dir, 'Library', 'Application Support')
+    # On Linux and as fallback
+    if not (path and os.path.isdir(path)):
+        path = user_dir
+
+    # Maybe we should store things local to the executable (in case of a
+    # portable distro or a frozen application that wants to be portable)
+    prefix = sys.prefix
+    if getattr(sys, 'frozen', None):  # See application_dir() function
+        prefix = os.path.abspath(os.path.dirname(sys.path[0]))
+    for reldir in ('settings', '../settings'):
+        localpath = os.path.abspath(os.path.join(prefix, reldir))
+        if os.path.isdir(localpath):
+            try:
+                open(os.path.join(localpath, 'test.write'), 'wb').close()
+                os.remove(os.path.join(localpath, 'test.write'))
+            except IOError:
+                pass  # We cannot write in this directory
+            else:
+                path = localpath
+                break
+
+    # Get path specific for this app
+    appname = '.vispy' if path == user_dir else 'vispy'
+    path = os.path.join(path, appname)
+    return path
+
+
+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, **kwargs):
+        self.events = EmitterGroup(source=self)
+        self.events['changed'] = EventEmitter(
+            event_class=ConfigEvent,
+            source=self)
+        self._config = {}
+        self.update(**kwargs)
+        self._known_keys = get_config_keys()
+
+    def __getitem__(self, item):
+        return self._config[item]
+
+    def __setitem__(self, item, val):
+        self._check_key_val(item, val)
+        self._config[item] = val
+        # inform any listeners that a configuration option has changed
+        self.events.changed(changes={item: val})
+
+    def _check_key_val(self, key, val):
+        # check values against acceptable ones
+        known_keys = get_config_keys()
+        if key not in known_keys:
+            raise KeyError('key "%s" not in known keys: "%s"'
+                           % (key, known_keys))
+        if not isinstance(val, (string_types, bool)):
+            raise TypeError('Value for key "%s" must be str or bool, not %s'
+                            % (key, type(val)))
+
+    def update(self, **kwargs):
+        for key, val in kwargs.items():
+            self._check_key_val(key, val)
+        self._config.update(kwargs)
+        self.events.changed(changes=kwargs)
+
+    def __repr__(self):
+        return repr(self._config)
+
+
+def get_config_keys():
+    """The config keys known by vispy
+
+    Returns
+    -------
+    keys : tuple
+        List of known config keys.
+    """
+    return ('data_path', 'default_backend', 'gl_debug', 'logging_level',
+            'qt_lib')
+
+
+def _get_config_fname():
+    """Helper for the vispy config file"""
+    directory = _get_vispy_app_dir()
+    if directory is None:
+        return None
+    fname = op.join(directory, 'vispy.json')
+    if os.environ.get('_VISPY_CONFIG_TESTING', None) is not None:
+        fname = op.join(_TempDir(), 'vispy.json')
+    return fname
+
+
+def _load_config():
+    """Helper to load prefs from ~/.vispy/vispy.json"""
+    fname = _get_config_fname()
+    if fname is None or not op.isfile(fname):
+        return dict()
+    with open(fname, 'r') as fid:
+        config = json.load(fid)
+    return config
+
+
+def save_config(**kwargs):
+    """Save configuration keys to vispy config file
+
+    Parameters
+    ----------
+    **kwargs : keyword arguments
+        Key/value pairs to save to the config file.
+    """
+    if kwargs == {}:
+        kwargs = config._config
+    current_config = _load_config()
+    current_config.update(**kwargs)
+    # write to disk
+    fname = _get_config_fname()
+    if fname is None:
+        raise RuntimeError('config filename could not be determined')
+    if not op.isdir(op.dirname(fname)):
+        os.mkdir(op.dirname(fname))
+    with open(fname, 'w') as fid:
+        json.dump(current_config, fid, sort_keys=True, indent=0)
+
+
+_data_path = _get_vispy_app_dir()
+if _data_path is not None:
+    _data_path = op.join(_data_path, 'data')
+config = Config(default_backend='qt', qt_lib='any',
+                gl_debug=False, logging_level='info',
+                data_path=_data_path)
+try:
+    config.update(**_load_config())
+except Exception as err:
+    raise Exception('Error while reading vispy config file "%s":\n  %s' %
+                    (_get_config_fname(), err.message))
+set_log_level(config['logging_level'])
+
+
+def set_data_dir(directory=None, create=False, save=False):
+    """Set vispy data download directory"""
+    if directory is None:
+        directory = _data_path
+        if _data_path is None:
+            raise IOError('default path cannot be determined, please '
+                          'set it manually (directory != None)')
+    if not op.isdir(directory):
+        if not create:
+            raise IOError('directory "%s" does not exist, perhaps try '
+                          'create=True to create it?' % directory)
+        os.mkdir(directory)
+    config.update(data_path=directory)
+    if save:
+        save_config(data_path=directory)
+
+
+###############################################################################
+# System information and parsing
+
+VISPY_HELP = """
+VisPy command line arguments:
+
+  --vispy-backend=(qt|pyqt|pyside|glut|glfw|pyglet)
+    Selects the backend system for VisPy to use. This will override the default
+    backend selection in your configuration file.
+    
+  --vispy-log=(debug|info|warning|error|critical)[,search string]
+    Sets the verbosity of logging output. The default is 'warning'. If a search
+    string is given, messages will only be displayed if they match the string,
+    or if their call location (module.class:method(line) or 
+    module:function(line)) matches the string.    
+    
+  --vispy-fps
+    Print the framerate (in Frames Per Second) in the console.
+    
+  --vispy-gl-debug
+    Enables error checking for all OpenGL calls.
+
+  --vispy-profile
+    Enable profiling and print the results when the program exits.
+    
+  --vispy-help
+    Display this help message.
+
+"""
+
+
+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.
+    """
+    # Get command line args for vispy
+    argnames = ['vispy-backend=', 'vispy-gl-debug', 'vispy-log=', 'vispy-help',
+                'vispy-profile']
+    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
+                logger.info('vispy backend: %s', a)
+            elif o == '--vispy-gl-debug':
+                config['gl_debug'] = True
+            elif o == '--vispy-log':
+                if ',' in a:
+                    verbose, match = a.split(',')
+                else:
+                    verbose = a
+                    match = None
+                config['logging_level'] = a
+                set_log_level(verbose, match)
+            elif o == '--vispy-profile':
+                _enable_profiling()
+            elif o == '--vispy-help':
+                print(VISPY_HELP)
+            else:
+                logger.warning("Unsupported vispy flag: %s" % o)
+
+
+def _enable_profiling():
+    """ Start profiling and register callback to print stats when the program
+    exits.
+    """
+    import cProfile
+    import atexit
+    global _profiler
+    _profiler = cProfile.Profile()
+    _profiler.enable()
+    atexit.register(_profile_atexit)
+
+
+_profiler = None
+
+
+def _profile_atexit():
+    global _profiler
+    _profiler.print_stats(sort='cumulative')
+
+
+def sys_info(fname=None, overwrite=False):
+    """Get relevant system and debugging information
+
+    Parameters
+    ----------
+    fname : str | None
+        Filename to dump info to. Use None to simply print.
+    overwrite : bool
+        If True, overwrite file (if it exists).
+
+    Returns
+    -------
+    out : str
+        The system information as a string.
+    """
+    if fname is not None and op.isfile(fname) and not overwrite:
+        raise IOError('file exists, use overwrite=True to overwrite')
+
+    out = ''
+    try:
+        # Nest all imports here to avoid any circular imports
+        from ..app import use_app, Canvas
+        from ..app.backends import BACKEND_NAMES
+        from ..gloo import gl
+        from ..testing import has_backend
+        # get default app
+        with use_log_level('warning'):
+            app = use_app()  # suppress messages
+        out += 'Platform: %s\n' % platform.platform()
+        out += 'Python:   %s\n' % str(sys.version).replace('\n', ' ')
+        out += 'Backend:  %s\n' % app.backend_name
+        for backend in BACKEND_NAMES:
+            with use_log_level('warning', print_msg=False):
+                which = has_backend(backend, out=['which'])[1]
+            out += '{0:<9} {1}\n'.format(backend + ':', which)
+        out += '\n'
+        # We need an OpenGL context to get GL info
+        if 'glut' in app.backend_name.lower():
+            # glut causes problems
+            out += 'OpenGL information omitted for glut backend\n'
+        else:
+            canvas = Canvas('Test', (10, 10), show=False, app=app)
+            canvas._backend._vispy_set_current()
+            out += 'GL version:  %s\n' % gl.glGetParameter(gl.GL_VERSION)
+            x_ = gl.GL_MAX_TEXTURE_SIZE
+            out += 'MAX_TEXTURE_SIZE: %d\n' % gl.glGetParameter(x_)
+            out += 'Extensions: %s\n' % gl.glGetParameter(gl.GL_EXTENSIONS)
+            canvas.close()
+    except Exception:  # don't stop printing info
+        out += '\nInfo-gathering error:\n%s' % traceback.format_exc()
+        pass
+    if fname is not None:
+        with open(fname, 'w') as fid:
+            fid.write(out)
+    return out
diff --git a/vispy/util/dataio/__init__.py b/vispy/util/dataio/__init__.py
deleted file mode 100644
index 6d0605b..0000000
--- a/vispy/util/dataio/__init__.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- 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/eq.py b/vispy/util/eq.py
new file mode 100644
index 0000000..bd26408
--- /dev/null
+++ b/vispy/util/eq.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+from numpy import ndarray, bool_
+
+
+def eq(a, b):
+    """ The great missing equivalence function: Guaranteed evaluation
+    to a single bool value.
+    """
+    if a is b:
+        return True
+    if a is None or b is None:
+        return True if a is None and b is None else False
+
+    try:
+        e = a == b
+    except ValueError:
+        return False
+    except AttributeError:
+        return False
+    except Exception:
+        print("a:", str(type(a)), str(a))
+        print("b:", str(type(b)), str(b))
+        raise
+    t = type(e)
+    if t is bool:
+        return e
+    elif t is bool_:
+        return bool(e)
+    elif isinstance(e, ndarray):
+        try:
+            # disaster: if a is empty and b is not, then e.all() is True
+            if a.shape != b.shape:
+                return False
+        except Exception:
+            return False
+        if (hasattr(e, 'implements') and e.implements('MetaArray')):
+            return e.asarray().all()
+        else:
+            return e.all()
+    else:
+        raise Exception("== operator returned type %s" % str(type(e)))
diff --git a/vispy/util/event.py b/vispy/util/event.py
index d454195..8985a28 100644
--- a/vispy/util/event.py
+++ b/vispy/util/event.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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 module 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.
@@ -12,57 +12,56 @@ For more information see http://github.com/vispy/vispy/wiki/API_Events
 
 """
 
-from __future__ import print_function, division, absolute_import
+from __future__ import division
 
-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
+import traceback
 
+from .logs import logger, _handle_exception
+from ..ext.ordereddict import OrderedDict
+from ..ext.six import string_types
 
 
 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`, 
+
+    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.
-    
+    functions is the responsibility of :class:`EventEmitter` instances.
+
     Note that each event object has an attribute for each of the input
     arguments listed below.
-    
-    Input arguments
-    ---------------
+
+    Parameters
+    ----------
     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._sources = []
         self._handled = False
         self._blocked = False
         # Store args
         self._type = type
         self._native = native
-        for k,v in kwds.items():
+        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).
+        """The object that the event applies to (i.e. the source of the event).
         """
         return self._sources[-1] if self._sources else None
 
@@ -73,37 +72,37 @@ class Event(object):
         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. 
+        """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
@@ -112,16 +111,17 @@ class Event(object):
         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 
+        # 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.
+        # need to keep track of depth because it is
+        # very difficult to avoid excessive recursion.
+        global _event_repr_depth
         _event_repr_depth += 1
         try:
             if _event_repr_depth > 2:
@@ -131,10 +131,11 @@ class Event(object):
                 if name.startswith('_'):
                     continue
                 # select only properties
-                if not hasattr(type(self), name) or not isinstance(getattr(type(self), name), property):
+                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:
@@ -142,34 +143,36 @@ class Event(object):
 
 _event_repr_depth = 0
 
+
 class EventEmitter(object):
-    """Encapsulates a list of event callbacks. 
-    
+
+    """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 
+    event to the list of callbacks that are registered to listen for that
+    event::
+
+       User clicks    |Canvas creates
+       mouse on       |MouseEvent:                |'mouse_press' EventEmitter:         |callbacks in sequence: # noqa
+       Canvas         |                           |                                    |  # noqa
+                   -->|event = MouseEvent(...) -->|Canvas.events.mouse_press(event) -->|callback1(event)  # noqa
+                      |                           |                                 -->|callback2(event)  # noqa
+                      |                           |                                 -->|callback3(event)  # noqa
+
+    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
-    ---------------
+    argument which will be an instance of :class:`Event <vispy.event.Event>`.
+
+    EventEmitters are generally created by an EmitterGroup instance.
+
+    Parameters
+    ----------
     source : object
         The object that the generated events apply to. All emitted Events will
         have their .source property set to this value.
@@ -177,148 +180,275 @@ class EventEmitter(object):
         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._callbacks = []
+        self._callback_refs = []
+
+        # count number of times this emitter is blocked for each callback.
+        self._blocked = {None: 0}
+
+        # used to detect emitter loops
+        self._emitting = False
         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
-        
+
+        self._ignore_callback_errors = True
+        self.print_callback_errors = 'reminders'
+
     @property
-    def source(self):
-        """ The object that events generated by this emitter apply to.
+    def ignore_callback_errors(self):
+        """Whether exceptions during callbacks will be caught by the emitter
+
+        This allows it to continue invoking other callbacks if an error
+        occurs.
+        """
+        return self._ignore_callback_errors
+
+    @ignore_callback_errors.setter
+    def ignore_callback_errors(self, val):
+        self._ignore_callback_errors = val
+
+    @property
+    def print_callback_errors(self):
+        """Print a message and stack trace if a callback raises an exception
+
+        Valid values are "first" (only show first instance), "reminders" (show
+        complete first instance, then counts), "always" (always show full
+        traceback), or "never".
+
+        This assumes ignore_callback_errors=True. These will be raised as
+        warnings, so ensure that the vispy logging level is set to at
+        least "warning".
         """
-        return None if self._source is None else self._source()  # get object behind weakref
-    
+        return self._print_callback_errors
+
+    @print_callback_errors.setter
+    def print_callback_errors(self, val):
+        if val not in ('first', 'reminders', 'always', 'never'):
+            raise ValueError('print_callback_errors must be "first", '
+                             '"reminders", "always", or "never"')
+        self._print_callback_errors = val
+
+    @property
+    def callback_refs(self):
+        """The set of callback references"""
+        return tuple(self._callback_refs)
+
+    @property
+    def callbacks(self):
+        """The set of callbacks"""
+        return tuple(self._callbacks)
+
+    @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.
+
+    def connect(self, callback, ref=False, position='first',
+                before=None, after=None):
+        """Connect this emitter to a new callback.
+
+        Parameters
+        ----------
+        callback : function | tuple
+            *callback* may be either a callable object or a tuple
+            (object, attr_name) where object.attr_name will point to a
+            callable object.
+        ref : bool | str
+            Reference used to identify the callback in ``before``/``after``.
+            If True, the callback ref will automatically determined (see
+            Notes). If False, the callback cannot be referred to by a string.
+            If str, the given string will be used. Note that if ``ref``
+            is not unique in ``callback_refs``, an error will be thrown.
+        position : str
+            If ``'first'``, the first eligible position is used (that
+            meets the before and after criteria), ``'last'`` will use
+            the last position.
+        before : str | callback | list of str or callback | None
+            List of callbacks that the current callback should precede.
+            Can be None if no before-criteria should be used.
+        after : str | callback | list of str or callback | None
+            List of callbacks that the current callback should follow.
+            Can be None if no after-criteria should be used.
+
+        Notes
+        -----
+        If ``ref=True``, the callback reference will be determined from:
+
+            1. If ``callback`` is ``tuple``, the secend element in the tuple.
+            2. The ``__name__`` attribute.
+            3. The ``__class__.__name__`` attribute.
+
+        The current list of callback refs can be obtained using
+        ``event.callback_refs``. Callbacks can be referred to by either
+        their string reference (if given), or by the actual callback that
+        was attached (e.g., ``(canvas, 'swap_buffers')``).
+
+        If the specified callback is already connected, then the request is
+        ignored.
+
+        If before is None and after is None (default), 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:
+        callbacks = self.callbacks
+        callback_refs = self.callback_refs
+        if callback in callbacks:
             return
-        self.callbacks.insert(0, callback)
-        return callback  ## allows connect to be used as a decorator
-    
+        # deal with the ref
+        if isinstance(ref, bool):
+            if ref:
+                if isinstance(callback, tuple):
+                    ref = callback[1]
+                elif hasattr(callback, '__name__'):  # function
+                    ref = callback.__name__
+                else:  # Method, or other
+                    ref = callback.__class__.__name__
+            else:
+                ref = None
+        elif not isinstance(ref, string_types):
+            raise TypeError('ref must be a bool or string')
+        if ref is not None and ref in self._callback_refs:
+            raise ValueError('ref "%s" is not unique' % ref)
+
+        # positions
+        if position not in ('first', 'last'):
+            raise ValueError('position must be "first" or "last", not %s'
+                             % position)
+
+        # bounds
+        bounds = list()  # upper & lower bnds (inclusive) of possible cb locs
+        for ri, criteria in enumerate((before, after)):
+            if criteria is None or criteria == []:
+                bounds.append(len(callback_refs) if ri == 0 else 0)
+            else:
+                if not isinstance(criteria, list):
+                    criteria = [criteria]
+                for c in criteria:
+                    count = sum([(c == cn or c == cc) for cn, cc
+                                 in zip(callback_refs, callbacks)])
+                    if count != 1:
+                        raise ValueError('criteria "%s" is in the current '
+                                         'callback list %s times:\n%s\n%s'
+                                         % (criteria, count,
+                                            callback_refs, callbacks))
+                matches = [ci for ci, (cn, cc) in enumerate(zip(callback_refs,
+                                                                callbacks))
+                           if (cc in criteria or cn in criteria)]
+                bounds.append(matches[0] if ri == 0 else (matches[-1] + 1))
+        if bounds[0] < bounds[1]:  # i.e., "place before" < "place after"
+            raise RuntimeError('cannot place callback before "%s" '
+                               'and after "%s" for callbacks: %s'
+                               % (before, after, callback_refs))
+        idx = bounds[1] if position == 'first' else bounds[0]  # 'last'
+
+        # actually add the callback
+        self._callbacks.insert(idx, callback)
+        self._callback_refs.insert(idx, ref)
+        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 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 = []
+            self._callbacks = []
+            self._callback_refs = []
         else:
-            try:
-                self.callbacks.remove(callback)
-            except ValueError:
-                pass
-            
+            if callback in self._callbacks:
+                idx = self._callbacks.index(callback)
+                self._callbacks.pop(idx)
+                self._callback_refs.pop(idx)
+
     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 
+        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. 
+        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.
+
+        # 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:
+            if self.blocked():
                 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
-                
+
+            for cb in self._callbacks:
+                if self.blocked(cb):
+                    continue
+                self._invoke_callback(cb, event)
                 if event.blocked:
                     break
         finally:
             self._emitting = False
             if event._pop_source() != self.source:
                 raise RuntimeError("Event source-stack mismatch.")
-            
+
         return event
-    
+
+    def _invoke_callback(self, cb, event):
+        if isinstance(cb, tuple):
+            cb = getattr(cb[0], cb[1], None)
+            if cb is None:
+                return
+
+        try:
+            cb(event)
+        except Exception:
+            # get traceback and store (so we can do postmortem
+            # debugging)
+            #import pdb
+            #import PyQt4
+            #PyQt4.QtCore.pyqtRemoveInputHook()
+            #pdb.post_mortem()
+            #PyQt4.QtCore.pyqtRestoreInputHook()
+            _handle_exception(self.ignore_callback_errors,
+                              self.print_callback_errors,
+                              self, cb_event=(cb, 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.
+        # 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
@@ -328,195 +458,261 @@ class EventEmitter(object):
             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.")
+            raise ValueError("Event emitters can be called with an Event "
+                             "instance or with keyword arguments only.")
         return event
-    
-    def block(self):
+
+    def blocked(self, callback=None):
+        """Return boolean indicating whether the emitter is blocked for
+        the given callback.
+        """
+        return self._blocked.get(callback, 0) > 0
+
+    def block(self, callback=None):
         """Block this emitter. Any attempts to emit an event while blocked
-        will be silently ignored. 
-        
+        will be silently ignored. If *callback* is given, then the emitter
+        is only blocked for that specific callback.
+
         Calls to block are cumulative; the emitter must be unblocked the same
-        number of times as it is blocked. 
+        number of times as it is blocked.
         """
-        self.blocked += 1
-        
-    def unblock(self):
+        self._blocked[callback] = self._blocked.get(callback, 0) + 1
+
+    def unblock(self, callback=None):
         """ Unblock this emitter. See :func:`event.EventEmitter.block`.
+        
+        Note: Use of ``unblock(None)`` only reverses the effect of 
+        ``block(None)``; it does not unblock callbacks that were explicitly 
+        blocked using ``block(callback)``. 
         """
-        self.blocked = max(0, self.blocked-1)
+        if callback not in self._blocked or self._blocked[callback] == 0:
+            raise RuntimeError("Cannot unblock %s for callback %s; emitter "
+                               "was not previously blocked." % 
+                               (self, callback))
+        b = self._blocked[callback] - 1
+        if b == 0 and callback is not None:
+            del self._blocked[callback]
+        else:
+            self._blocked[callback] = b
+
+    def blocker(self, callback=None):
+        """Return an EventBlocker to be used in 'with' statements
+
+           Notes
+           -----
+           For example, one could do::
 
-    def blocker(self):
-        """Return an EventBlocker to be used in 'with' statements::
-        
                with emitter.blocker():
-                   ..do stuff; no events will be emitted..
-        
+                   pass  # ..do stuff; no events will be emitted..
         """
-        return EventBlocker(self)
+        return EventBlocker(self, callback)
+
+
+class WarningEmitter(EventEmitter):
+    """
+    EventEmitter subclass used to allow deprecated events to be used with a
+    warning message.
+    """
+    def __init__(self, message, *args, **kwds):
+        self._message = message
+        self._warned = False
+        EventEmitter.__init__(self, *args, **kwds)
+
+    def connect(self, cb, *args, **kwds):
+        self._warn(cb)
+        return EventEmitter.connect(self, cb, *args, **kwds)
+
+    def _invoke_callback(self, cb, event):
+        self._warn(cb)
+        return EventEmitter._invoke_callback(self, cb, event)
+
+    def _warn(self, cb):
+        if self._warned:
+            return
+
+        # don't warn about unimplemented connections
+        if isinstance(cb, tuple) and getattr(cb[0], cb[1], None) is None:
+            return
+
+        traceback.print_stack()
+        logger.warning(self._message)
+        self._warned = True
 
 
 class EmitterGroup(EventEmitter):
-    """EmitterGroup instances manage a set of related 
-    :class:`EventEmitters <vispy.event.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 
+    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>`, 
+
+    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
-    ---------------
+    events. Any callback that connects directly to the EmitterGroup will
+    receive *all* of the events generated by the group's emitters.
+
+    Parameters
+    ----------
     source : object
-        The object that the generated events apply to.  
+        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)) 
+        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
-                                          
+        # whether the sub-emitters have been connected to the group:
+        self._emitters_connected = False
         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 
+        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, 
+        Each keyword argument may be specified as either an EventEmitter
+        instance or an Event subclass, in which case an EventEmitter will be
+        generated automatically::
+
+            # 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))
+            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)
+                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)
-            
+                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)
+                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.
-            
+                raise Exception('Emitter must be specified as either an '
+                                'EventEmitter instance or Event subclass. '
+                                '(got %s=%s)' % (name, emitter))
+
+            # give this emitter the same source as the group.
+            emitter.source = self.source
+
             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 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):
+
+    def connect(self, callback, ref=False, position='first',
+                before=None, after=None):
         """ 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.
+        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)
+        return EventEmitter.connect(self, callback, ref, position,
+                                    before, after)
 
     def disconnect(self, callback=None):
-        """ Disconnect the callback from this group. See 
-        :func:`connect() <vispy.event.EmitterGroup.connect>` and 
+        """ 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:
+        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, 
+        # 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:
@@ -524,20 +720,21 @@ class EmitterGroup(EventEmitter):
         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):
+    def __init__(self, target, callback=None):
         self.target = target
-        
+        self.callback = callback
+
     def __enter__(self):
-        self.target.block()
-        
-    def __exit__(self, *args):
-        self.target.unblock()
+        self.target.block(self.callback)
 
+    def __exit__(self, *args):
+        self.target.unblock(self.callback)
diff --git a/vispy/util/fetching.py b/vispy/util/fetching.py
new file mode 100644
index 0000000..4bdc506
--- /dev/null
+++ b/vispy/util/fetching.py
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""Data downloading and reading functions
+"""
+
+from math import log
+import os
+from os import path as op
+import sys
+import shutil
+
+from ..ext.six.moves import urllib
+from ..util.config import config
+
+
+###############################################################################
+# Vispy data directory
+
+def load_data_file(fname, directory=None, force_download=False):
+    """Get a standard vispy demo data file
+
+    Parameters
+    ----------
+    fname : str
+        The filename on the remote ``demo-data`` repository to download,
+        e.g. ``'molecular_viewer/micelle.npy'``. These correspond to paths
+        on ``https://github.com/vispy/demo-data/``.
+    directory : str | None
+        Directory to use to save the file. By default, the vispy
+        configuration directory is used.
+    force_download : bool
+        If True, the file will be downloaded even if a local copy exists
+        (and this copy will be overwritten).
+
+    Returns
+    -------
+    fname : str
+        The path to the file on the local system.
+    """
+    _url_root = 'https://github.com/vispy/demo-data/raw/master/'
+    url = _url_root + fname
+    if directory is None:
+        directory = config['data_path']
+        if directory is None:
+            raise ValueError('config["data_path"] is not defined, '
+                             'so directory must be supplied')
+
+    fname = op.join(directory, op.normcase(fname))  # convert to native
+    if op.isfile(fname) and not force_download:  # we're done
+        return fname
+    if not op.isdir(op.dirname(fname)):
+        os.makedirs(op.abspath(op.dirname(fname)))
+    # let's go get the file
+    _fetch_file(url, fname)
+    return fname
+
+
+def get_testing_file(fname, directory=None, force_download=False):
+    """Get a standard vispy test data file
+
+    Parameters
+    ----------
+    fname : str
+        The filename on the remote ``test-data`` repository to download,
+        e.g. ``'visuals/square.png'``. These correspond to paths
+        on ``https://github.com/vispy/test-data/``.
+    directory : str | None
+        Directory to use to save the file. By default, the vispy
+        configuration directory is used.
+    force_download : bool
+        If True, the file will be downloaded even if a local copy exists
+        (and this copy will be overwritten).
+
+    Returns
+    -------
+    fname : str
+        The path to the file on the local system.
+    """
+    _url_root = 'https://github.com/vispy/test-data/raw/master/'
+    url = _url_root + fname
+    if directory is None:
+        directory = config['data_path']
+        if directory is None:
+            raise ValueError('config["data_path"] is not defined, '
+                             'so directory must be supplied')
+
+    fname = op.join(directory, op.normcase(fname))  # convert to native
+    if op.isfile(fname) and not force_download:  # we're done
+        return fname
+    if not op.isdir(op.dirname(fname)):
+        os.makedirs(op.abspath(op.dirname(fname)))
+    # let's go get the file
+    _fetch_file(url, fname)
+    return fname
+
+###############################################################################
+# File downloading (most adapted from mne-python)
+
+
+class ProgressBar(object):
+    """Class for generating a command-line progressbar
+
+    Parameters
+    ----------
+    max_value : int
+        Maximum value of process (e.g. number of samples to process, bytes to
+        download, etc.).
+    initial_value : int
+        Initial value of process, useful when resuming process from a specific
+        value, defaults to 0.
+    mesg : str
+        Message to include at end of progress bar.
+    max_chars : int
+        Number of characters to use for progress bar (be sure to save some room
+        for the message and % complete as well).
+    progress_character : char
+        Character in the progress bar that indicates the portion completed.
+    spinner : bool
+        Show a spinner.  Useful for long-running processes that may not
+        increment the progress bar very often.  This provides the user with
+        feedback that the progress has not stalled.
+    """
+    spinner_symbols = ['|', '/', '-', '\\']
+    template = '\r[{0}{1}] {2:.05f} {3} {4}   '
+
+    def __init__(self, max_value, initial_value=0, mesg='', max_chars=40,
+                 progress_character='.', spinner=False):
+        self.cur_value = initial_value
+        self.max_value = float(max_value)
+        self.mesg = mesg
+        self.max_chars = max_chars
+        self.progress_character = progress_character
+        self.spinner = spinner
+        self.spinner_index = 0
+        self.n_spinner = len(self.spinner_symbols)
+
+    def update(self, cur_value, mesg=None):
+        """Update progressbar with current value of process
+
+        Parameters
+        ----------
+        cur_value : number
+            Current value of process.  Should be <= max_value (but this is not
+            enforced).  The percent of the progressbar will be computed as
+            (cur_value / max_value) * 100
+        mesg : str
+            Message to display to the right of the progressbar.  If None, the
+            last message provided will be used.  To clear the current message,
+            pass a null string, ''.
+        """
+        # Ensure floating-point division so we can get fractions of a percent
+        # for the progressbar.
+        self.cur_value = cur_value
+        progress = float(self.cur_value) / self.max_value
+        num_chars = int(progress * self.max_chars)
+        num_left = self.max_chars - num_chars
+
+        # Update the message
+        if mesg is not None:
+            self.mesg = mesg
+
+        # The \r tells the cursor to return to the beginning of the line rather
+        # than starting a new line.  This allows us to have a progressbar-style
+        # display in the console window.
+        bar = self.template.format(self.progress_character * num_chars,
+                                   ' ' * num_left,
+                                   progress * 100,
+                                   self.spinner_symbols[self.spinner_index],
+                                   self.mesg)
+        sys.stdout.write(bar)
+        # Increament the spinner
+        if self.spinner:
+            self.spinner_index = (self.spinner_index + 1) % self.n_spinner
+
+        # Force a flush because sometimes when using bash scripts and pipes,
+        # the output is not printed until after the program exits.
+        sys.stdout.flush()
+
+    def update_with_increment_value(self, increment_value, mesg=None):
+        """Update progressbar with the value of the increment instead of the
+        current value of process as in update()
+
+        Parameters
+        ----------
+        increment_value : int
+            Value of the increment of process.  The percent of the progressbar
+            will be computed as
+            (self.cur_value + increment_value / max_value) * 100
+        mesg : str
+            Message to display to the right of the progressbar.  If None, the
+            last message provided will be used.  To clear the current message,
+            pass a null string, ''.
+        """
+        self.cur_value += increment_value
+        self.update(self.cur_value, mesg)
+
+
+def _chunk_read(response, local_file, chunk_size=65536, initial_size=0):
+    """Download a file chunk by chunk and show advancement
+
+    Can also be used when resuming downloads over http.
+
+    Parameters
+    ----------
+    response: urllib.response.addinfourl
+        Response to the download request in order to get file size.
+    local_file: file
+        Hard disk file where data should be written.
+    chunk_size: integer, optional
+        Size of downloaded chunks. Default: 8192
+    initial_size: int, optional
+        If resuming, indicate the initial size of the file.
+    """
+    # Adapted from NISL:
+    # https://github.com/nisl/tutorial/blob/master/nisl/datasets.py
+
+    bytes_so_far = initial_size
+    # Returns only amount left to download when resuming, not the size of the
+    # entire file
+    total_size = int(response.headers['Content-Length'].strip())
+    total_size += initial_size
+
+    progress = ProgressBar(total_size, initial_value=bytes_so_far,
+                           max_chars=40, spinner=True, mesg='downloading')
+    while True:
+        chunk = response.read(chunk_size)
+        bytes_so_far += len(chunk)
+        if not chunk:
+            sys.stderr.write('\n')
+            break
+        _chunk_write(chunk, local_file, progress)
+
+
+def _chunk_write(chunk, local_file, progress):
+    """Write a chunk to file and update the progress bar"""
+    local_file.write(chunk)
+    progress.update_with_increment_value(len(chunk))
+
+
+def _fetch_file(url, file_name, print_destination=True):
+    """Load requested file, downloading it if needed or requested
+
+    Parameters
+    ----------
+    url: string
+        The url of file to be downloaded.
+    file_name: string
+        Name, along with the path, of where downloaded file will be saved.
+    print_destination: bool, optional
+        If true, destination of where file was saved will be printed after
+        download finishes.
+    resume: bool, optional
+        If true, try to resume partially downloaded files.
+    """
+    # Adapted from NISL:
+    # https://github.com/nisl/tutorial/blob/master/nisl/datasets.py
+
+    temp_file_name = file_name + ".part"
+    local_file = None
+    initial_size = 0
+    try:
+        # Checking file size and displaying it alongside the download url
+        u = urllib.request.urlopen(url, timeout=5.)
+        file_size = int(u.headers['Content-Length'].strip())
+        print('Downloading data from %s (%s)' % (url, sizeof_fmt(file_size)))
+        # Downloading data (can be extended to resume if need be)
+        local_file = open(temp_file_name, "wb")
+        data = urllib.request.urlopen(url, timeout=5.)
+        _chunk_read(data, local_file, initial_size=initial_size)
+        # temp file must be closed prior to the move
+        if not local_file.closed:
+            local_file.close()
+        shutil.move(temp_file_name, file_name)
+        if print_destination is True:
+            sys.stdout.write('File saved as %s.\n' % file_name)
+    except Exception as e:
+        raise RuntimeError('Error while fetching file %s.\n'
+                           'Dataset fetching aborted (%s)' % (url, e))
+    finally:
+        if local_file is not None:
+            if not local_file.closed:
+                local_file.close()
+
+
+def sizeof_fmt(num):
+    """Turn number of bytes into human-readable str"""
+    units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
+    decimals = [0, 0, 1, 2, 2, 2]
+    """Human friendly file size"""
+    if num > 1:
+        exponent = min(int(log(num, 1024)), len(units) - 1)
+        quotient = float(num) / 1024 ** exponent
+        unit = units[exponent]
+        num_decimals = decimals[exponent]
+        format_string = '{0:.%sf} {1}' % (num_decimals)
+        return format_string.format(quotient, unit)
+    return '0 bytes' if num == 0 else '1 byte'
diff --git a/vispy/util/filter.py b/vispy/util/filter.py
new file mode 100644
index 0000000..c9bb7a9
--- /dev/null
+++ b/vispy/util/filter.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+
+
+def gaussian_filter(data, sigma):
+    """
+    Drop-in replacement for scipy.ndimage.gaussian_filter.
+
+    (note: results are only approximately equal to the output of
+     gaussian_filter)
+    """
+    if np.isscalar(sigma):
+        sigma = (sigma,) * data.ndim
+
+    baseline = data.mean()
+    filtered = data - baseline
+    for ax in range(data.ndim):
+        s = float(sigma[ax])
+        if s == 0:
+            continue
+
+        # generate 1D gaussian kernel
+        ksize = int(s * 6)
+        x = np.arange(-ksize, ksize)
+        kernel = np.exp(-x**2 / (2*s**2))
+        kshape = [1, ] * data.ndim
+        kshape[ax] = len(kernel)
+        kernel = kernel.reshape(kshape)
+
+        # convolve as product of FFTs
+        shape = data.shape[ax] + ksize
+        scale = 1.0 / (abs(s) * (2*np.pi)**0.5)
+        filtered = scale * np.fft.irfft(np.fft.rfft(filtered, shape, axis=ax) *
+                                        np.fft.rfft(kernel, shape, axis=ax),
+                                        axis=ax)
+
+        # clip off extra data
+        sl = [slice(None)] * data.ndim
+        sl[ax] = slice(filtered.shape[ax]-data.shape[ax], None, None)
+        filtered = filtered[sl]
+    return filtered + baseline
diff --git a/vispy/util/fonts/__init__.py b/vispy/util/fonts/__init__.py
new file mode 100644
index 0000000..6491ff5
--- /dev/null
+++ b/vispy/util/fonts/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+"""
+The fonts module implements some helpful functions for dealing with system
+fonts.
+"""
+
+__all__ = ['list_fonts']
+
+from ._triage import _load_glyph, list_fonts  # noqa, analysis:ignore
+from ._vispy_fonts import _vispy_fonts  # noqa, analysis:ignore
diff --git a/vispy/util/fonts/_freetype.py b/vispy/util/fonts/_freetype.py
new file mode 100644
index 0000000..b47d665
--- /dev/null
+++ b/vispy/util/fonts/_freetype.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+# Use freetype to get glyph bitmaps
+
+import sys
+import numpy as np
+
+
+# Convert face to filename
+from ._vispy_fonts import _vispy_fonts, _get_vispy_font_filename
+if sys.platform.startswith('linux'):
+    from ...ext.fontconfig import find_font
+elif sys.platform.startswith('win'):
+    from ._win32 import find_font  # noqa, analysis:ignore
+else:
+    raise NotImplementedError
+
+_font_dict = {}
+
+
+# Nest freetype imports in case someone doesn't have freetype on their system
+# and isn't using fonts (Windows)
+
+def _load_font(face, bold, italic):
+    from ...ext.freetype import Face
+    key = '%s-%s-%s' % (face, bold, italic)
+    if key in _font_dict:
+        return _font_dict[key]
+    if face in _vispy_fonts:
+        fname = _get_vispy_font_filename(face, bold, italic)
+    else:
+        fname = find_font(face, bold, italic)
+    font = Face(fname)
+    _font_dict[key] = font
+    return font
+
+
+def _load_glyph(f, char, glyphs_dict):
+    """Load glyph from font into dict"""
+    from ...ext.freetype import (FT_LOAD_RENDER, FT_LOAD_NO_HINTING,
+                                 FT_LOAD_NO_AUTOHINT)
+    flags = FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT
+    face = _load_font(f['face'], f['bold'], f['italic'])
+    face.set_char_size(f['size'] * 64)
+    # get the character of interest
+    face.load_char(char, flags)
+    bitmap = face.glyph.bitmap
+    width = face.glyph.bitmap.width
+    height = face.glyph.bitmap.rows
+    bitmap = np.array(bitmap.buffer)
+    w0 = bitmap.size // height if bitmap.size > 0 else 0
+    bitmap.shape = (height, w0)
+    bitmap = bitmap[:, :width].astype(np.ubyte)
+
+    left = face.glyph.bitmap_left
+    top = face.glyph.bitmap_top
+    advance = face.glyph.advance.x / 64.
+    glyph = dict(char=char, offset=(left, top), bitmap=bitmap,
+                 advance=advance, kerning={})
+    glyphs_dict[char] = glyph
+    # Generate kerning
+    for other_char, other_glyph in glyphs_dict.items():
+        kerning = face.get_kerning(other_char, char)
+        glyph['kerning'][other_char] = kerning.x / 64.
+        kerning = face.get_kerning(char, other_char)
+        other_glyph['kerning'][char] = kerning.x / 64.
diff --git a/vispy/util/fonts/_quartz.py b/vispy/util/fonts/_quartz.py
new file mode 100644
index 0000000..d7c862e
--- /dev/null
+++ b/vispy/util/fonts/_quartz.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+# Use OSX cocoa/quartz to get glyph bitmaps
+
+import numpy as np
+from ctypes import byref, c_int32, c_byte
+
+from ...ext.cocoapy import cf, ct, quartz, CFRange, CFSTR, CGGlyph, UniChar, \
+    kCTFontFamilyNameAttribute, kCTFontBoldTrait, kCTFontItalicTrait, \
+    kCTFontSymbolicTrait, kCTFontTraitsAttribute, kCTFontAttributeName, \
+    kCGImageAlphaPremultipliedLast, kCFNumberSInt32Type, ObjCClass
+from ._vispy_fonts import _vispy_fonts, _get_vispy_font_filename
+
+_font_dict = {}
+
+
+def _load_vispy_font(face, bold, italic):
+    # http://stackoverflow.com/questions/2703085/
+    # how-can-you-load-a-font-ttf-from-a-file-using-core-text
+    fname = _get_vispy_font_filename(face, bold, italic)
+    url = cf.CFURLCreateWithFileSystemPath(None, CFSTR(fname), 0, False)
+    # data_provider = quartz.CGDataProviderCreateWithURL(url)
+    # cg_font = quartz.CGFontCreateWithDataProvider(data_provider)
+    # font = ct.CTFontCreateWithGraphicsFont(cg_font, 12., None, None)
+    array = ct.CTFontManagerCreateFontDescriptorsFromURL(url)
+    desc = cf.CFArrayGetValueAtIndex(array, 0)
+    font = ct.CTFontCreateWithFontDescriptor(desc, 12., None)
+    cf.CFRelease(array)
+    cf.CFRelease(url)
+    if not font:
+        raise RuntimeError("Couldn't load font: %s" % face)
+    key = '%s-%s-%s' % (face, bold, italic)
+    _font_dict[key] = font
+    return font
+
+
+def _load_font(face, bold, italic):
+    key = '%s-%s-%s' % (face, bold, italic)
+    if key in _font_dict:
+        return _font_dict[key]
+    if face in _vispy_fonts:
+        return _load_vispy_font(face, bold, italic)
+    traits = 0
+    traits |= kCTFontBoldTrait if bold else 0
+    traits |= kCTFontItalicTrait if italic else 0
+
+    # Create an attribute dictionary.
+    args = [None, 0, cf.kCFTypeDictionaryKeyCallBacks,
+            cf.kCFTypeDictionaryValueCallBacks]
+    attributes = cf.CFDictionaryCreateMutable(*args)
+    # Add family name to attributes.
+    cfname = CFSTR(face)
+    cf.CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, cfname)
+    cf.CFRelease(cfname)
+    # Construct a CFNumber to represent the traits.
+    itraits = c_int32(traits)
+    sym_traits = cf.CFNumberCreate(None, kCFNumberSInt32Type, byref(itraits))
+    if sym_traits:
+        # Construct a dictionary to hold the traits values.
+        args = [None, 0, cf.kCFTypeDictionaryKeyCallBacks,
+                cf.kCFTypeDictionaryValueCallBacks]
+        traits_dict = cf.CFDictionaryCreateMutable(*args)
+        if traits_dict:
+            # Add CFNumber traits to traits dictionary.
+            cf.CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait,
+                                    sym_traits)
+            # Add traits dictionary to attributes.
+            cf.CFDictionaryAddValue(attributes, kCTFontTraitsAttribute,
+                                    traits_dict)
+            cf.CFRelease(traits_dict)
+        cf.CFRelease(sym_traits)
+    # Create font descriptor with attributes.
+    desc = ct.CTFontDescriptorCreateWithAttributes(attributes)
+    cf.CFRelease(attributes)
+    font = ct.CTFontCreateWithFontDescriptor(desc, 12., None)
+    if not font:
+        raise RuntimeError("Couldn't load font: %s" % face)
+    _font_dict[key] = font
+    return font
+
+
+def _load_glyph(f, char, glyphs_dict):
+    font = _load_font(f['face'], f['bold'], f['italic'])
+    # resize loaded font
+    args = [None, 0, cf.kCFTypeDictionaryKeyCallBacks,
+            cf.kCFTypeDictionaryValueCallBacks]
+    attributes = cf.CFDictionaryCreateMutable(*args)
+    desc = ct.CTFontDescriptorCreateWithAttributes(attributes)
+    cf.CFRelease(attributes)
+    font = ct.CTFontCreateCopyWithAttributes(font, f['size'], None, desc)
+    cf.CFRelease(desc)
+    if not font:
+        raise RuntimeError("Couldn't load font")
+    # Create an attributed string using text and font.
+    args = [None, 1, cf.kCFTypeDictionaryKeyCallBacks,
+            cf.kCFTypeDictionaryValueCallBacks]
+    attributes = cf.CFDictionaryCreateMutable(*args)
+    cf.CFDictionaryAddValue(attributes, kCTFontAttributeName, font)
+    string = cf.CFAttributedStringCreate(None, CFSTR(char), attributes)
+    # Create a CTLine object to render the string.
+    line = ct.CTLineCreateWithAttributedString(string)
+    cf.CFRelease(string)
+    cf.CFRelease(attributes)
+    # Get a bounding rectangle for glyphs in string.
+    chars = (UniChar * 1)(*map(ord, char))
+    glyphs = (CGGlyph * 1)()
+    ct.CTFontGetGlyphsForCharacters(font, chars, glyphs, 1)
+    rect = ct.CTFontGetBoundingRectsForGlyphs(font, 0, glyphs, None, 1)
+    # Get advance for all glyphs in string.
+    advance = ct.CTFontGetAdvancesForGlyphs(font, 1, glyphs, None, 1)
+    width = max(int(np.ceil(rect.size.width) + 1), 1)
+    height = max(int(np.ceil(rect.size.height) + 1), 1)
+
+    left = rect.origin.x
+    baseline = -rect.origin.y
+    top = height - baseline
+
+    bits_per_component = 8
+    bytes_per_row = 4*width
+    color_space = quartz.CGColorSpaceCreateDeviceRGB()
+    args = [None, width, height, bits_per_component, bytes_per_row,
+            color_space, kCGImageAlphaPremultipliedLast]
+    bitmap = quartz.CGBitmapContextCreate(*args)
+    # Draw text to bitmap context.
+    quartz.CGContextSetShouldAntialias(bitmap, True)
+    quartz.CGContextSetTextPosition(bitmap, -left, baseline)
+    ct.CTLineDraw(line, bitmap)
+    cf.CFRelease(line)
+    # Create an image to get the data out.
+    image_ref = quartz.CGBitmapContextCreateImage(bitmap)
+    assert quartz.CGImageGetBytesPerRow(image_ref) == bytes_per_row
+    data_provider = quartz.CGImageGetDataProvider(image_ref)
+    image_data = quartz.CGDataProviderCopyData(data_provider)
+    buffer_size = cf.CFDataGetLength(image_data)
+    assert buffer_size == width * height * 4
+    buffer = (c_byte * buffer_size)()
+    byte_range = CFRange(0, buffer_size)
+    cf.CFDataGetBytes(image_data, byte_range, buffer)
+    quartz.CGImageRelease(image_ref)
+    quartz.CGDataProviderRelease(image_data)
+    cf.CFRelease(bitmap)
+    cf.CFRelease(color_space)
+
+    # reshape bitmap (don't know why it's only alpha on OSX...)
+    bitmap = np.array(buffer, np.ubyte)
+    bitmap.shape = (height, width, 4)
+    bitmap = bitmap[:, :, 3].copy()
+    glyph = dict(char=char, offset=(left, top), bitmap=bitmap,
+                 advance=advance, kerning={})
+    glyphs_dict[char] = glyph
+    # Generate kerning
+    for other_char, other_glyph in glyphs_dict.items():
+        glyph['kerning'][other_char] = (_get_k_p_a(font, other_char, char) -
+                                        other_glyph['advance'])
+        other_glyph['kerning'][char] = (_get_k_p_a(font, char, other_char) -
+                                        glyph['advance'])
+    cf.CFRelease(font)
+
+
+def _get_k_p_a(font, left, right):
+    """This actually calculates the kerning + advance"""
+    # http://lists.apple.com/archives/coretext-dev/2010/Dec/msg00020.html
+    # 1) set up a CTTypesetter
+    chars = left + right
+    args = [None, 1, cf.kCFTypeDictionaryKeyCallBacks,
+            cf.kCFTypeDictionaryValueCallBacks]
+    attributes = cf.CFDictionaryCreateMutable(*args)
+    cf.CFDictionaryAddValue(attributes, kCTFontAttributeName, font)
+    string = cf.CFAttributedStringCreate(None, CFSTR(chars), attributes)
+    typesetter = ct.CTTypesetterCreateWithAttributedString(string)
+    cf.CFRelease(string)
+    cf.CFRelease(attributes)
+    # 2) extract a CTLine from it
+    range = CFRange(0, 1)
+    line = ct.CTTypesetterCreateLine(typesetter, range)
+    # 3) use CTLineGetOffsetForStringIndex to get the character positions
+    offset = ct.CTLineGetOffsetForStringIndex(line, 1, None)
+    cf.CFRelease(line)
+    cf.CFRelease(typesetter)
+    return offset
+
+
+def _list_fonts():
+    manager = ObjCClass('NSFontManager').sharedFontManager()
+    avail = manager.availableFontFamilies()
+    fonts = [avail.objectAtIndex_(ii).UTF8String().decode('utf-8')
+             for ii in range(avail.count())]
+    return fonts
diff --git a/vispy/util/fonts/_triage.py b/vispy/util/fonts/_triage.py
new file mode 100644
index 0000000..f9c696f
--- /dev/null
+++ b/vispy/util/fonts/_triage.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+import sys
+
+from ._vispy_fonts import _vispy_fonts
+if sys.platform.startswith('linux'):
+    from ._freetype import _load_glyph
+    from ...ext.fontconfig import _list_fonts
+elif sys.platform == 'darwin':
+    from ._quartz import _load_glyph, _list_fonts
+elif sys.platform.startswith('win'):
+    from ._freetype import _load_glyph  # noqa, analysis:ignore
+    from ._win32 import _list_fonts  # noqa, analysis:ignore
+else:
+    raise NotImplementedError('unknown system %s' % sys.platform)
+
+_fonts = {}
+
+
+def list_fonts():
+    """List system fonts
+
+    Returns
+    -------
+    fonts : list of str
+        List of system fonts.
+    """
+    vals = _list_fonts()
+    for font in _vispy_fonts:
+        vals += [font] if font not in vals else []
+    vals = sorted(vals, key=lambda s: s.lower())
+    return vals
diff --git a/vispy/util/fonts/_vispy_fonts.py b/vispy/util/fonts/_vispy_fonts.py
new file mode 100644
index 0000000..19023b7
--- /dev/null
+++ b/vispy/util/fonts/_vispy_fonts.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+from ..fetching import load_data_file
+
+# List the vispy fonts made available online
+_vispy_fonts = ('OpenSans', 'Cabin')
+
+
+def _get_vispy_font_filename(face, bold, italic):
+    """Fetch a remote vispy font"""
+    name = face + '-'
+    name += 'Regular' if not bold and not italic else ''
+    name += 'Bold' if bold else ''
+    name += 'Italic' if italic else ''
+    name += '.ttf'
+    return load_data_file('fonts/%s' % name)
diff --git a/vispy/util/fonts/_win32.py b/vispy/util/fonts/_win32.py
new file mode 100644
index 0000000..8a51760
--- /dev/null
+++ b/vispy/util/fonts/_win32.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014, Vispy Development Team. All Rights Reserved.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+# -----------------------------------------------------------------------------
+
+import os
+from os import path as op
+import warnings
+
+
+from ctypes import (cast, byref, sizeof, create_unicode_buffer,
+                    c_void_p, c_wchar_p)
+from ...ext.gdi32plus import (gdiplus, gdi32, user32, winreg, LOGFONT,
+                              OUTLINETEXTMETRIC, GM_ADVANCED, FW_NORMAL,
+                              FW_BOLD, LF_FACESIZE, DEFAULT_CHARSET,
+                              TRUETYPE_FONTTYPE, FONTENUMPROC, BOOL)
+
+
+# Inspired by:
+# http://forums.codeguru.com/showthread.php?90792-How-to-get-a-system-
+# font-file-name-given-a-LOGFONT-face-name
+
+# XXX This isn't perfect, but it should work for now...
+
+def find_font(face, bold, italic):
+    style_dict = {'Regular': 0, 'Bold': 1, 'Italic': 2, 'Bold Italic': 3}
+
+    # Figure out which font to actually use by trying to instantiate by name
+    dc = user32.GetDC(0)  # noqa, analysis:ignore
+    gdi32.SetGraphicsMode(dc, GM_ADVANCED)  # only TT and OT fonts
+    logfont = LOGFONT()
+    logfont.lfHeight = -12  # conv point to pixels
+    logfont.lfWeight = FW_BOLD if bold else FW_NORMAL
+    logfont.lfItalic = italic
+    logfont.lfFaceName = face  # logfont needs Unicode
+    hfont = gdi32.CreateFontIndirectW(byref(logfont))
+    original = gdi32.SelectObject(dc, hfont)
+    n_byte = gdi32.GetOutlineTextMetricsW(dc, 0, None)
+    assert n_byte > 0
+    metrics = OUTLINETEXTMETRIC()
+    assert sizeof(metrics) >= n_byte
+    assert gdi32.GetOutlineTextMetricsW(dc, n_byte, byref(metrics))
+    gdi32.SelectObject(dc, original)
+    use_face = cast(byref(metrics, metrics.otmpFamilyName), c_wchar_p).value
+    if use_face != face:
+        warnings.warn('Could not find face match "%s", falling back to "%s"'
+                      % (face, use_face))
+    use_style = cast(byref(metrics, metrics.otmpStyleName), c_wchar_p).value
+    use_style = style_dict.get(use_style, 'Regular')
+    # AK: I get "Standaard" for use_style, which is Dutch for standard/regular
+
+    # Now we match by creating private font collections until we find
+    # the one that was used
+    font_dir = op.join(os.environ['WINDIR'], 'Fonts')
+    reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+    key = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts'
+    reg_vals = winreg.OpenKey(reg, key)
+    n_values = winreg.QueryInfoKey(reg_vals)[1]
+    fname = None
+    for vi in range(n_values):
+        name, ff = winreg.EnumValue(reg_vals, vi)[:2]
+        if name.endswith('(TrueType)'):
+            ff = op.join(font_dir, ff) if op.basename(ff) == ff else ff
+            assert op.isfile(ff)
+            pc = c_void_p()
+            assert gdiplus.GdipNewPrivateFontCollection(byref(pc)) == 0
+            gdiplus.GdipPrivateAddFontFile(pc, ff)
+            family = c_void_p()
+            if gdiplus.GdipCreateFontFamilyFromName(use_face, pc,
+                                                    byref(family)) == 0:
+                val = BOOL()
+                assert gdiplus.GdipIsStyleAvailable(family, use_style,
+                                                    byref(val)) == 0
+                if val.value:
+                    buf = create_unicode_buffer(LF_FACESIZE)
+                    assert gdiplus.GdipGetFamilyName(family, buf, 0) == 0
+                    assert buf.value == use_face
+                    fname = ff
+    if fname is None:
+        raise RuntimeError('Could not find system font %s' % use_face)
+    return fname
+
+
+def _list_fonts():
+    dc = user32.GetDC(0)
+    gdi32.SetGraphicsMode(dc, GM_ADVANCED)  # only TT and OT fonts
+    logfont = LOGFONT()
+    logfont.lfCharSet = DEFAULT_CHARSET
+    logfont.lfFaceName = ''
+    logfont.lfPitchandFamily = 0
+    fonts = list()
+
+    def enum_fun(lp_logfont, lp_text_metric, font_type, l_param):
+        # Only support TTF for now (silly Windows shortcomings)
+        if font_type == TRUETYPE_FONTTYPE:
+            font = lp_logfont.contents.lfFaceName
+            if not font.startswith('@') and font not in fonts:
+                fonts.append(font)
+        return 1
+
+    gdi32.EnumFontFamiliesExW(dc, byref(logfont), FONTENUMPROC(enum_fun), 0, 0)
+    return fonts
diff --git a/vispy/shaders/__init__.py b/vispy/util/fonts/tests/__init__.py
similarity index 100%
copy from vispy/shaders/__init__.py
copy to vispy/util/fonts/tests/__init__.py
diff --git a/vispy/util/fonts/tests/test_font.py b/vispy/util/fonts/tests/test_font.py
new file mode 100644
index 0000000..9333b93
--- /dev/null
+++ b/vispy/util/fonts/tests/test_font.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import numpy as np
+from nose.tools import assert_true, assert_equal
+import warnings
+
+from vispy.testing import assert_in
+from vispy.util.fonts import list_fonts, _load_glyph, _vispy_fonts
+
+warnings.simplefilter('always')
+
+
+def test_font_list():
+    """Test font listing"""
+    f = list_fonts()
+    assert_true(len(f) > 0)
+    for font in _vispy_fonts:
+        assert_in(font, f)
+
+
+def test_font_glyph():
+    """Test loading glyphs"""
+    # try both a vispy and system font
+    sys_fonts = set(list_fonts()) - set(_vispy_fonts)
+    assert_true(len(sys_fonts) > 0)
+    for face in ('OpenSans', list(sys_fonts)[0]):
+        font_dict = dict(face=face, size=12, bold=False, italic=False)
+        glyphs_dict = dict()
+        chars = 'foobar^C&#'
+        for char in chars:
+            # Warning that Arial might not exist
+            _load_glyph(font_dict, char, glyphs_dict)
+        assert_equal(len(glyphs_dict), np.unique([c for c in chars]).size)
diff --git a/vispy/util/keys.py b/vispy/util/keys.py
index 3c3bce7..5ed7a92 100644
--- a/vispy/util/keys.py
+++ b/vispy/util/keys.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, Vispy Development Team.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
 """ Define constants for keys.
@@ -14,46 +14,33 @@ 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.
-    
+from ..ext.six import string_types
+
+
+class Key(object):
+    """ Represent the identity of a certain key.
+
+    This 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.
+        """ The primary name of the key.
         """
         return self._names[0]
     
-    def __repr__(self):        
+    def __hash__(self):
+        return self._names[0].__hash__()
+    
+    def __repr__(self):
         return "<Key %s>" % ', '.join([repr(v) for v in self._names])
     
     def __eq__(self, other):
@@ -62,9 +49,11 @@ class Key:
         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]
+            return other in [ord(v) for v in self._names_upper if len(v) == 1]
+        elif other is None:
+            return False
         else:
-            raise ValueError('Key constants can only be compared to str, int and Key.')
+            raise ValueError('Key can only be compared to str, int and Key.')
 
 
 SHIFT = Key('Shift')
diff --git a/vispy/util/logs.py b/vispy/util/logs.py
new file mode 100644
index 0000000..b9443a1
--- /dev/null
+++ b/vispy/util/logs.py
@@ -0,0 +1,328 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+import logging
+import math
+import sys
+import inspect
+import re
+import traceback
+from functools import partial
+
+from ..ext.six import string_types
+
+
+###############################################################################
+# LOGGING (some adapted from mne-python)
+
+def _get_vispy_caller():
+    """Helper to get vispy calling function from the stack"""
+    records = inspect.stack()
+    # first few records are vispy-based logging calls
+    for record in records[5:]:
+        module = record[0].f_globals['__name__']
+        if module.startswith('vispy'):
+            line = str(record[0].f_lineno)
+            func = record[3]
+            caller = "{0}:{1}({2}): ".format(module, func, line)
+            return caller
+    return 'unknown'
+
+
+# class _WrapStdOut(object):
+#     """Class to work around how doctest captures stdout"""
+#     def __getattr__(self, name):
+#         # Even more ridiculous than this class, this must be sys.stdout (not
+#         # just stdout) in order for this to work (tested on OSX and Linux)
+#         return getattr(sys.stdout, name)
+
+
+class _VispyFormatter(logging.Formatter):
+    """Formatter that optionally prepends caller"""
+    def __init__(self):
+        logging.Formatter.__init__(self, '%(levelname)s: %(message)s')
+        self._vispy_prepend_caller = False
+
+    def _vispy_set_prepend(self, prepend):
+        self._vispy_prepend_caller = prepend
+
+    def format(self, record):
+        out = logging.Formatter.format(self, record)
+        if self._vispy_prepend_caller:
+            out = _get_vispy_caller() + out
+        return out
+
+
+class _VispyStreamHandler(logging.StreamHandler):
+    """Stream handler allowing matching and recording
+
+    This handler has two useful optional additions:
+
+        1. Recording emitted messages.
+        2. Performing regexp substring matching.
+
+    Prepending of traceback information is done in _VispyFormatter.
+    """
+    def __init__(self):
+        logging.StreamHandler.__init__(self, sys.stderr)
+        self._vispy_formatter = _lf
+        self.setFormatter(self._vispy_formatter)
+        self._vispy_match = None
+        self._vispy_emit_list = list()
+        self._vispy_set_emit_record(False)
+        self._vispy_set_match(None)
+        self._vispy_print_msg = True
+
+    def _vispy_emit_match_andor_record(self, record):
+        """Log message emitter that optionally matches and/or records"""
+        test = record.getMessage()
+        match = self._vispy_match
+        if (match is None or re.search(match, test) or
+                re.search(match, _get_vispy_caller())):
+            if self._vispy_emit_record:
+                fmt_rec = self._vispy_formatter.format(record)
+                self._vispy_emit_list.append(fmt_rec)
+            if self._vispy_print_msg:
+                return logging.StreamHandler.emit(self, record)
+            else:
+                return
+
+    def _vispy_set_match(self, match):
+        old_match = self._vispy_match
+        self._vispy_match = match
+        # Triage here to avoid a bunch of if's later (more efficient)
+        if match is not None or self._vispy_emit_record:
+            self.emit = self._vispy_emit_match_andor_record
+        else:
+            self.emit = partial(logging.StreamHandler.emit, self)
+        return old_match
+
+    def _vispy_set_emit_record(self, record):
+        self._vispy_emit_record = record
+        match = self._vispy_match
+        # Triage here to avoid a bunch of if's later (more efficient)
+        if match is not None or self._vispy_emit_record:
+            self.emit = self._vispy_emit_match_andor_record
+        else:
+            self.emit = partial(logging.StreamHandler.emit, self)
+
+    def _vispy_reset_list(self):
+        self._vispy_emit_list = list()
+
+
+logger = logging.getLogger('vispy')
+_lf = _VispyFormatter()
+_lh = _VispyStreamHandler()  # needs _lf to exist
+logger.addHandler(_lh)
+
+logging_types = dict(debug=logging.DEBUG, info=logging.INFO,
+                     warning=logging.WARNING, error=logging.ERROR,
+                     critical=logging.CRITICAL)
+
+
+def set_log_level(verbose, match=None, return_old=False):
+    """Convenience function for setting the logging level
+
+    Parameters
+    ----------
+    verbose : bool, str, int, or None
+        The verbosity of messages to print. If a str, it can be either DEBUG,
+        INFO, WARNING, ERROR, or CRITICAL. Note that these are for
+        convenience and are equivalent to passing in logging.DEBUG, etc.
+        For bool, True is the same as 'INFO', False is the same as 'WARNING'.
+    match : str | None
+        String to match. Only those messages that both contain a substring
+        that regexp matches ``'match'`` (and the ``verbose`` level) will be
+        displayed.
+    return_old_level : bool
+        If True, return the old verbosity level and old match.
+
+    Notes
+    -----
+    If ``verbose=='debug'``, then the ``vispy`` method emitting the log
+    message will be prepended to each log message, which is useful for
+    debugging. If ``verbose=='debug'`` or ``match is not None``, then a
+    small performance overhead is added. Thus it is suggested to only use
+    these options when performance is not crucial.
+
+    See also
+    --------
+    vispy.util.use_log_level
+    """
+    # This method is responsible for setting properties of the handler and
+    # formatter such that proper messages (possibly with the vispy caller
+    # prepended) are displayed. Storing log messages is only available
+    # via the context handler (use_log_level), so that configuration is
+    # done by the context handler itself.
+    if isinstance(verbose, bool):
+        verbose = 'info' if verbose else 'warning'
+    if isinstance(verbose, string_types):
+        verbose = verbose.lower()
+        if verbose not in logging_types:
+            raise ValueError('Invalid argument "%s"' % verbose)
+        verbose = logging_types[verbose]
+    else:
+        raise TypeError('verbose must be a bool or string')
+    logger = logging.getLogger('vispy')
+    old_verbose = logger.level
+    old_match = _lh._vispy_set_match(match)
+    logger.setLevel(verbose)
+    if verbose <= logging.DEBUG:
+        _lf._vispy_set_prepend(True)
+    else:
+        _lf._vispy_set_prepend(False)
+    out = None
+    if return_old:
+        out = (old_verbose, old_match)
+    return out
+
+
+class use_log_level(object):
+    """Context manager that temporarily sets logging level
+
+    Parameters
+    ----------
+    level : str
+        See ``set_log_level`` for options.
+    record : bool
+        If True, the context manager will keep a record of the logging
+        messages generated by vispy. Otherwise, an empty list will
+        be returned.
+    print_msg : bool
+        If False, printing of (all) messages will be suppressed. This is
+        mainly useful in testing. False only works in `record=True` mode, if
+        not recording messages, consider setting `level` appropriately.
+
+    Returns
+    -------
+    records : list
+        As a context manager, an empty list or the list of logging messages
+        will be returned (depending on the input ``record``).
+    """
+    # This method mostly wraps to set_log_level, but also takes
+    # care of enabling/disabling message recording in the formatter.
+    def __init__(self, level, match=None, record=False, print_msg=True):
+        self._new_level = level
+        self._new_match = match
+        self._print_msg = print_msg
+        self._record = record
+        if match is not None and not isinstance(match, string_types):
+            raise TypeError('match must be None or str')
+
+    def __enter__(self):
+        # set the log level
+        old_level, old_match = set_log_level(self._new_level,
+                                             self._new_match, return_old=True)
+        for key, value in logging_types.items():
+            if value == old_level:
+                old_level = key
+        self._old_level = old_level
+        self._old_match = old_match
+        if not self._print_msg:
+            _lh._vispy_print_msg = False
+        # set handler to record, if appropriate
+        _lh._vispy_reset_list()
+        if self._record:
+            _lh._vispy_set_emit_record(True)
+            return _lh._vispy_emit_list
+        else:
+            return list()
+
+    def __exit__(self, type, value, traceback):
+        # reset log level
+        set_log_level(self._old_level, self._old_match)
+        # reset handler
+        if self._record:
+            _lh._vispy_set_emit_record(False)
+        if not self._print_msg:
+            _lh._vispy_print_msg = True  # set it back
+
+
+def log_exception(level='warning', tb_skip=2):
+    """
+    Send an exception and traceback to the logger.
+    
+    This function is used in cases where an exception is handled safely but
+    nevertheless should generate a descriptive error message. An extra line
+    is inserted into the stack trace indicating where the exception was caught.
+    
+    Parameters
+    ----------
+    level : str
+        See ``set_log_level`` for options.
+    tb_skip : int
+        The number of traceback entries to ignore, prior to the point where
+        the exception was caught. The default is 2.
+    """
+    stack = "".join(traceback.format_stack()[:-tb_skip])
+    tb = traceback.format_exception(*sys.exc_info())
+    msg = tb[0]  # "Traceback (most recent call last):"
+    msg += stack
+    msg += "  << caught exception here: >>\n"
+    msg += "".join(tb[1:]).rstrip()
+    logger.log(logging_types[level], msg)
+
+logger.log_exception = log_exception  # make this easier to reach
+
+
+def _handle_exception(ignore_callback_errors, print_callback_errors, obj,
+                      cb_event=None, entity=None):
+    """Helper for prining errors in callbacks
+
+    See EventEmitter._invoke_callback for a use example.
+    """
+    if not hasattr(obj, '_vispy_err_registry'):
+        obj._vispy_err_registry = {}
+    registry = obj._vispy_err_registry
+
+    if cb_event is not None:
+        cb, event = cb_event
+        exp_type = 'callback'
+    else:
+        exp_type = 'entity'
+    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
+    del tb  # Get rid of it in this namespace
+    # Handle
+    if not ignore_callback_errors:
+        raise
+    if print_callback_errors != "never":
+        this_print = 'full'
+        if print_callback_errors in ('first', 'reminders'):
+            # need to check to see if we've hit this yet
+            if exp_type == 'callback':
+                key = repr(cb) + repr(event)
+            else:
+                key = repr(entity)
+            if key in registry:
+                registry[key] += 1
+                if print_callback_errors == 'first':
+                    this_print = None
+                else:  # reminders
+                    ii = registry[key]
+                    # Use logarithmic selection
+                    # (1, 2, ..., 10, 20, ..., 100, 200, ...)
+                    if ii % (10 ** int(math.log10(ii))) == 0:
+                        this_print = ii
+                    else:
+                        this_print = None
+            else:
+                registry[key] = 1
+        if this_print == 'full':
+            logger.log_exception()
+            if exp_type == 'callback':
+                logger.warning("Error invoking callback %s for "
+                               "event: %s" % (cb, event))
+            else:  # == 'entity':
+                logger.warning("Error drawing entity %s" % entity)
+        elif this_print is not None:
+            if exp_type == 'callback':
+                logger.warning("Error invoking callback %s repeat %s"
+                               % (cb, this_print))
+            else:  # == 'entity':
+                logger.warning("Error drawing entity %s repeat %s"
+                               % (entity, this_print))
diff --git a/vispy/util/ordereddict.py b/vispy/util/ordereddict.py
deleted file mode 100644
index 5b0303f..0000000
--- a/vispy/util/ordereddict.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# 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
index 36ef0da..093105f 100644
--- a/vispy/util/ptime.py
+++ b/vispy/util/ptime.py
@@ -1,32 +1,40 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2013, Vispy Development Team.
+# Copyright (c) 2014, 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)
+ptime.py -  Precision time function made os-independent
+(should have been taken care of by python)
 """
 
-from __future__ import print_function, division, absolute_import
+from __future__ import division
 
 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 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()
+    # return systime.time()
+
 
 def unixTime():
-    """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent)."""
+    """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
+    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/test/test_vispy.py b/vispy/util/test/test_vispy.py
deleted file mode 100644
index 88a8f57..0000000
--- a/vispy/util/test/test_vispy.py
+++ /dev/null
@@ -1,6 +0,0 @@
-""" Tests to ensure that base vispy namespace functions correctly,
-including configuration options.
-"""
-
-
-
diff --git a/vispy/shaders/__init__.py b/vispy/util/tests/__init__.py
similarity index 100%
rename from vispy/shaders/__init__.py
rename to vispy/util/tests/__init__.py
diff --git a/vispy/util/tests/test_config.py b/vispy/util/tests/test_config.py
new file mode 100644
index 0000000..0036867
--- /dev/null
+++ b/vispy/util/tests/test_config.py
@@ -0,0 +1,51 @@
+from nose.tools import assert_raises, assert_equal, assert_true
+from os import path as op
+import os
+
+from vispy.util import config, sys_info, _TempDir, set_data_dir, save_config
+from vispy.util.fetching import load_data_file
+from vispy.testing import assert_in, requires_application
+temp_dir = _TempDir()
+
+
+ at requires_application()
+def test_sys_info():
+    """Test printing of system information"""
+    fname = op.join(temp_dir, 'info.txt')
+    sys_info(fname)
+    assert_raises(IOError, sys_info, fname)  # no overwrite
+    with open(fname, 'r') as fid:
+        out = ''.join(fid.readlines())
+    # Note: 'GL version' only for non-GLUT
+    keys = ['Python', 'Backend', 'pyglet', 'Platform:']
+    for key in keys:
+        assert_in(key, out)
+    print(out)
+    assert_true('Info-gathering error' not in out)
+
+
+def test_config():
+    """Test vispy config methods and file downloading"""
+    assert_raises(TypeError, config.update, data_path=dict())
+    assert_raises(KeyError, config.update, foo='bar')  # bad key
+    data_dir = op.join(temp_dir, 'data')
+    assert_raises(IOError, set_data_dir, data_dir)  # didn't say to create
+    orig_val = os.environ.get('_VISPY_CONFIG_TESTING', None)
+    os.environ['_VISPY_CONFIG_TESTING'] = 'true'
+    try:
+        assert_raises(IOError, set_data_dir, data_dir)  # doesn't exist yet
+        set_data_dir(data_dir, create=True, save=True)
+        assert_equal(config['data_path'], data_dir)
+        config['data_path'] = data_dir
+        print(config)  # __repr__
+        load_data_file('CONTRIBUTING.txt')
+        fid = open(op.join(data_dir, 'test-faked.txt'), 'w')
+        fid.close()
+        load_data_file('test-faked.txt')  # this one shouldn't download
+        assert_raises(RuntimeError, load_data_file, 'foo-nonexist.txt')
+        save_config()
+    finally:
+        if orig_val is not None:
+            os.environ['_VISPY_CONFIG_TESTING'] = orig_val
+        else:
+            del os.environ['_VISPY_CONFIG_TESTING']
diff --git a/vispy/util/test/test_emitter_group.py b/vispy/util/tests/test_emitter_group.py
similarity index 83%
rename from vispy/util/test/test_emitter_group.py
rename to vispy/util/tests/test_emitter_group.py
index d6b58ba..8e98bfb 100644
--- a/vispy/util/test/test_emitter_group.py
+++ b/vispy/util/tests/test_emitter_group.py
@@ -1,23 +1,28 @@
-from vispy.util.event import Event, EventEmitter, EmitterGroup
 import unittest
 import copy
-import functools
+
+from vispy.util.event import Event, EventEmitter, EmitterGroup
+
 
 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)
@@ -27,8 +32,11 @@ class TestGroups(unittest.TestCase):
         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)
-        
+        self.assert_result(
+            event=ev,
+            type='typed_event',
+            event_class=TypedEvent)
+
     def test_group_add_emitter(self):
         """EmitterGroup.add"""
         grp = EmitterGroup(em1=Event)
@@ -36,38 +44,43 @@ class TestGroups(unittest.TestCase):
         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)
-        
+        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:
@@ -77,11 +90,11 @@ class TestGroups(unittest.TestCase):
         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
@@ -90,14 +103,17 @@ class TestGroups(unittest.TestCase):
         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()
@@ -105,26 +121,26 @@ class TestGroups(unittest.TestCase):
         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
@@ -138,21 +154,35 @@ class TestGroups(unittest.TestCase):
         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)
-        
+        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)
-        
+        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)
-                
+        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
@@ -160,7 +190,7 @@ class TestGroups(unittest.TestCase):
         names = [name for name in dir(ev) if name[0] != '_']
         attrs = {}
         for name in names:
-            val = getattr(ev,name)
+            val = getattr(ev, name)
             if name == 'source':
                 attrs[name] = val
             elif name == 'sources':
@@ -168,10 +198,10 @@ class TestGroups(unittest.TestCase):
             else:
                 try:
                     attrs[name] = copy.deepcopy(val)
-                except:
+                except Exception:
                     try:
                         attrs[name] = copy.copy(val)
-                    except:
+                    except Exception:
                         attrs[name] = val
         if key is None:
             self.result = ev, attrs
@@ -179,27 +209,27 @@ class TestGroups(unittest.TestCase):
             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"
-        
+        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():
+
+        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__
-                
+                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))
-        
-        
-            
+                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/tests/test_event_emitter.py
similarity index 60%
rename from vispy/util/test/test_event_emitter.py
rename to vispy/util/tests/test_event_emitter.py
index ea75ae2..1b25664 100644
--- a/vispy/util/test/test_event_emitter.py
+++ b/vispy/util/tests/test_event_emitter.py
@@ -1,81 +1,115 @@
-from vispy.util.event import Event, EventEmitter
+from nose.tools import assert_raises, assert_equal
 import unittest
 import copy
 import functools
 
+from vispy.util.event import Event, EventEmitter
+
+
 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.
+
+        # 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])
-        
-    
+        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])
-        
+        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])
-        
+        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])
+        self.assert_result(
+            event=ev,
+            event_class=Event,
+            source=None,
+            type='qwer',
+            sources=[None])
 
-    def test_emitter_type(self):
+    def test_emitter_type_event_class(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])
-        
+        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
+                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:
+            self.assert_result()  # checks event type
+            assert False, \
+                "Should not be able to construct emitter with non-Event class"
+        except Exception:
             pass
-        
+
     def test_event_kwargs(self):
         """Extra Event kwargs"""
         em = EventEmitter(type='test_event')
@@ -84,22 +118,23 @@ class TestEmitters(unittest.TestCase):
         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
@@ -113,62 +148,71 @@ class TestEmitters(unittest.TestCase):
     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])
+        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)
+        em.disconnect(cb2)  # should pass silently
         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
-        
+        assert self.result is 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')
@@ -177,29 +221,42 @@ class TestEmitters(unittest.TestCase):
         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])
+        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])
+        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
+        em.print_callback_errors = 'never'
+
         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
@@ -209,15 +266,17 @@ class TestEmitters(unittest.TestCase):
         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
@@ -230,7 +289,7 @@ class TestEmitters(unittest.TestCase):
         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')
@@ -248,11 +307,11 @@ class TestEmitters(unittest.TestCase):
         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:
@@ -265,36 +324,37 @@ class TestEmitters(unittest.TestCase):
     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.
+
+        # cross-connect emitters; when we emit, an exception should be raised
+        # indicating an event loop.
         em1.connect(em2)
         em2.connect(em1)
         try:
@@ -302,25 +362,27 @@ class TestEmitters(unittest.TestCase):
         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
@@ -329,34 +391,35 @@ class TestEmitters(unittest.TestCase):
         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
@@ -364,7 +427,7 @@ class TestEmitters(unittest.TestCase):
         names = [name for name in dir(ev) if name[0] != '_']
         attrs = {}
         for name in names:
-            val = getattr(ev,name)
+            val = getattr(ev, name)
             if name == 'source':
                 attrs[name] = val
             elif name == 'sources':
@@ -372,10 +435,10 @@ class TestEmitters(unittest.TestCase):
             else:
                 try:
                     attrs[name] = copy.deepcopy(val)
-                except:
+                except Exception:
                     try:
                         attrs[name] = copy.copy(val)
-                    except:
+                    except Exception:
                         attrs[name] = val
         if key is None:
             self.result = ev, attrs
@@ -383,39 +446,215 @@ class TestEmitters(unittest.TestCase):
             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"
-        
+        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():
+
+        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__
-                
+                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))
-        
+                assert (attr == val), "Event.%s != %s  (%s)" % (
+                    name, str(val), str(attr))
+
+
+def test_event_connect_order():
+    """Test event connection order"""
+    def a():
+        return
+
+    def b():
+        return
+
+    def c():
+        return
+
+    def d():
+        return
+
+    def e():
+        return
+
+    def f():
+        return
+
+    em = EventEmitter(type='test_event')
+    assert_raises(ValueError, em.connect, c, before=['c', 'foo'])
+    assert_raises(ValueError, em.connect, c, position='foo')
+    assert_raises(TypeError, em.connect, c, ref=dict())
+    em.connect(c, ref=True)
+    assert_equal((c,), tuple(em.callbacks))
+    em.connect(c)
+    assert_equal((c,), tuple(em.callbacks))
+    em.connect(d, ref=True, position='last')
+    assert_equal((c, d), tuple(em.callbacks))
+    em.connect(b, ref=True)  # position='first'
+    assert_equal((b, c, d), tuple(em.callbacks))
+    assert_raises(RuntimeError, em.connect, a, before='c', after='d')  # can't
+    em.connect(a, ref=True, before=['c', 'd'])  # first possible pos == 0
+    assert_equal((a, b, c, d), tuple(em.callbacks))
+    em.connect(f, ref=True, after=['c', 'd'])
+    assert_equal((a, b, c, d, f), tuple(em.callbacks))
+    em.connect(e, ref=True, after='d', before='f')
+    assert_equal(('a', 'b', 'c', 'd', 'e', 'f'), tuple(em.callback_refs))
+    em.disconnect(e)
+    em.connect(e, ref=True, after='a', before='f', position='last')
+    assert_equal(('a', 'b', 'c', 'd', 'e', 'f'), tuple(em.callback_refs))
+    em.disconnect(e)
+    em.connect(e, ref='e', after='d', before='f', position='last')
+    assert_equal(('a', 'b', 'c', 'd', 'e', 'f'), tuple(em.callback_refs))
+    em.disconnect(e)
+    em.connect(e, after='d', before='f', position='first')  # no name
+    assert_equal(('a', 'b', 'c', 'd', None, 'f'), tuple(em.callback_refs))
+    em.disconnect(e)
+    assert_raises(ValueError, em.connect, e, ref='d')  # duplicate name
+    em.connect(e, ref=True, after=[], before='f', position='last')
+    assert_equal(('a', 'b', 'c', 'd', 'e', 'f'), tuple(em.callback_refs))
+    assert_equal((a, b, c, d, e, f), tuple(em.callbacks))
+
+    old_e = e
+
+    def e():
+        return
+
+    assert_raises(ValueError, em.connect, e, ref=True)  # duplicate name
+    em.connect(e)
+    assert_equal((None, 'a', 'b', 'c', 'd', 'e', 'f'),
+                 tuple(em.callback_refs))
+    assert_equal((e, a, b, c, d, old_e, f), tuple(em.callbacks))
+
+
+def test_emitter_block():
+    state = [False, False]
+    
+    def a(ev):
+        state[0] = True
         
+    def b(ev):
+        state[1] = True
         
-#if __name__ == '__main__':
-    #import sys
-    #test = EventTest()
+    e = EventEmitter(source=None, type='event')
+    e.connect(a)
+    e.connect(b)
+    
+    def assert_state(a, b):
+        assert state == [a, b]
+        state[0] = False
+        state[1] = False
     
-    #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'
-            
+    e()
+    assert_state(True, True)
+    
+    # test global blocking
+    e.block()
+    e()
+    assert_state(False, False)
+    e.block()
+    e()
+    assert_state(False, False)
+    
+    # test global unlock, multiple depth
+    e.unblock()
+    e()
+    assert_state(False, False)
+    e.unblock()
+    e()
+    assert_state(True, True)
+    
+    # test unblock failure
+    try:
+        e.unblock()
+        raise Exception("Expected RuntimeError")
+    except RuntimeError:
+        pass
+    
+    # test single block
+    e.block(a)
+    e()
+    assert_state(False, True)
+    
+    e.block(b)
+    e()
+    assert_state(False, False)
+    
+    e.block(b)
+    e()
+    assert_state(False, False)
+
+    # test single unblock
+    e.unblock(a)
+    e()
+    assert_state(True, False)
+    
+    e.unblock(b)
+    e()
+    assert_state(True, False)
+    
+    e.unblock(b)
+    e()
+    assert_state(True, True)
+    
+    # Test single unblock failure
+    try:
+        e.unblock(a)
+        raise Exception("Expected RuntimeError")
+    except RuntimeError:
+        pass
+
+    # test global blocker
+    with e.blocker():
+        e()
+        assert_state(False, False)
+        
+        # test nested blocker
+        with e.blocker():
+            e()
+            assert_state(False, False)
+        
+        e()
+        assert_state(False, False)
+
+    e()
+    assert_state(True, True)
+
+    # test single blocker
+    with e.blocker(a):
+        e()
+        assert_state(False, True)
+        
+        # test nested gloabel blocker
+        with e.blocker():
+            e()
+            assert_state(False, False)
+
+        e()
+        assert_state(False, True)
+
+        # test nested single blocker
+        with e.blocker(a):
+            e()
+            assert_state(False, True)
+
+        with e.blocker(b):
+            e()
+            assert_state(False, False)
+
+        e()
+        assert_state(False, True)
+
+    e()
+    assert_state(True, True)
diff --git a/vispy/util/tests/test_import.py b/vispy/util/tests/test_import.py
new file mode 100644
index 0000000..0b3c69a
--- /dev/null
+++ b/vispy/util/tests/test_import.py
@@ -0,0 +1,135 @@
+"""
+Test that importing vispy subpackages do not pull
+in any more vispy submodules than strictly necessary.
+"""
+
+import sys
+import os
+import subprocess
+
+from nose.tools import assert_equal
+from vispy.testing import assert_in, assert_not_in, requires_pyopengl
+
+import vispy
+
+
+# minimum that will be imported when importing vispy
+_min_modules = ['vispy', 'vispy.util', 'vispy.ext']
+
+
+def check_output(*popenargs, **kwargs):
+    """ Minimal py 2.6 compatible version of subprocess.check_output()
+
+    Py2.6 does not have check_output.
+    Taken from https://gist.github.com/edufelipe/1027906
+    """
+    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+    output, unused_err = process.communicate()
+    retcode = process.poll()
+    if retcode:
+        cmd = kwargs.get("args")
+        if cmd is None:
+            cmd = popenargs[0]
+        error = subprocess.CalledProcessError(retcode, cmd)
+        error.output = output
+        raise error
+    return output
+
+
+def loaded_vispy_modules(import_module, depth=None, all_modules=False):
+    """ Import the given module in subprocess and return loaded modules
+
+    Import a certain module in a clean subprocess and return the
+    vispy modules that are subsequently loaded. The given depth
+    indicates the module level (i.e. depth=1 will only yield 'vispy.app'
+    but not 'vispy.app.backends').
+    """
+
+    vispy_dir = os.path.dirname(os.path.dirname(vispy.__file__))
+
+    # Get the loaded modules in a clean interpreter
+    code = "import sys, %s; print(', '.join(sys.modules))" % import_module
+    res = check_output([sys.executable, '-c', code], cwd=vispy_dir)
+    res = res.decode('utf-8')
+    loaded_modules = [name.strip() for name in res.split(',')]
+    
+    if all_modules:
+        return loaded_modules
+    
+    # Get only vispy modules at the given depth
+    vispy_modules = set()
+    for m in loaded_modules:
+        if m.startswith('vispy') and '__future__' not in m:
+            if depth:
+                parts = m.split('.')
+                m = '.'.join(parts[:depth])
+            vispy_modules.add(m)
+    
+    return vispy_modules
+
+
+def test_import_nothing():
+    """ Not importing vispy should not import any vispy modules. """
+    modnames = loaded_vispy_modules('os', 2)
+    assert_equal(modnames, set())
+
+
+def test_import_vispy():
+    """ Importing vispy should only pull in other vispy.util submodule. """
+    modnames = loaded_vispy_modules('vispy', 2)
+    assert_equal(modnames, set(_min_modules))
+
+
+def test_import_vispy_util():
+    """ Importing vispy.util should not pull in other vispy submodules. """
+    modnames = loaded_vispy_modules('vispy.util', 2)
+    assert_equal(modnames, set(_min_modules))
+
+
+def test_import_vispy_app1():
+    """ Importing vispy.app should not pull in other vispy submodules. """
+    modnames = loaded_vispy_modules('vispy.app', 2)
+    assert_equal(modnames, set(_min_modules + ['vispy.app']))
+
+
+def test_import_vispy_app2():
+    """ Importing vispy.app should not pull in any backend toolkit. """
+    allmodnames = loaded_vispy_modules('vispy.app', 2, True)
+    assert_not_in('PySide', allmodnames)
+    assert_not_in('PyQt4', allmodnames)
+    assert_not_in('pyglet', allmodnames)
+    assert_not_in('OpenGL.GLUT', allmodnames)
+
+
+def test_import_vispy_gloo():
+    """ Importing vispy.gloo should not pull in other vispy submodules. """
+    modnames = loaded_vispy_modules('vispy.gloo', 2)
+    assert_equal(modnames, set(_min_modules + ['vispy.gloo', 'vispy.color']))
+
+
+def test_import_vispy_no_pyopengl():
+    """ Importing vispy.gloo.gl.desktop should not import PyOpenGL. """
+    # vispy.gloo desktop backend
+    allmodnames = loaded_vispy_modules('vispy.gloo.gl.desktop', 2, True)
+    assert_not_in('OpenGL', allmodnames)
+    # vispy.app 
+    allmodnames = loaded_vispy_modules('vispy.app', 2, True)
+    assert_not_in('OpenGL', allmodnames)
+    # vispy.scene
+    allmodnames = loaded_vispy_modules('vispy.scene', 2, True)
+    assert_not_in('OpenGL', allmodnames)
+
+
+ at requires_pyopengl()
+def test_import_vispy_pyopengl():
+    """ Importing vispy.gloo.gl.pyopengl should import PyOpenGL. """
+    allmodnames = loaded_vispy_modules('vispy.gloo.gl.pyopengl', 2, True)
+    assert_in('OpenGL', allmodnames)
+
+
+def test_import_vispy_scene():
+    """ Importing vispy.gloo.gl.desktop should not import PyOpenGL. """
+    modnames = loaded_vispy_modules('vispy.scene', 2)
+    more_modules = ['vispy.app', 'vispy.gloo', 'vispy.scene', 'vispy.color', 
+                    'vispy.io', 'vispy.geometry']
+    assert_equal(modnames, set(_min_modules + more_modules))
diff --git a/vispy/util/tests/test_key.py b/vispy/util/tests/test_key.py
new file mode 100644
index 0000000..87679f6
--- /dev/null
+++ b/vispy/util/tests/test_key.py
@@ -0,0 +1,15 @@
+from nose.tools import assert_raises, assert_true, assert_equal
+
+from vispy.util.keys import Key, ENTER
+
+
+def test_key():
+    """Test basic key functionality"""
+    def bad():
+        return (ENTER == dict())
+    assert_raises(ValueError, bad)
+    assert_true(not (ENTER == None))  # noqa
+    assert_equal('Return', ENTER)
+    print(ENTER.name)
+    print(ENTER)  # __repr__
+    assert_equal(Key('1'), 49)  # ASCII code
diff --git a/vispy/util/tests/test_logging.py b/vispy/util/tests/test_logging.py
new file mode 100644
index 0000000..b6a158a
--- /dev/null
+++ b/vispy/util/tests/test_logging.py
@@ -0,0 +1,39 @@
+from nose.tools import assert_equal
+import logging
+
+from vispy.util import logger, use_log_level
+from vispy.testing import assert_in, assert_not_in
+
+
+def test_logging():
+    """Test logging context manager"""
+    ll = logger.level
+    with use_log_level('warning', print_msg=False):
+        assert_equal(logger.level, logging.WARN)
+    assert_equal(logger.level, ll)
+    with use_log_level('debug', print_msg=False):
+        assert_equal(logger.level, logging.DEBUG)
+    assert_equal(logger.level, ll)
+
+
+def test_debug_logging():
+    """Test advanced debugging logging"""
+    with use_log_level('debug', 'Selected', True, False) as l:
+        logger.debug('Selected foo')
+    assert_equal(len(l), 1)
+    assert_in('test_logging', l[0])  # can't really parse this location
+
+    with use_log_level('debug', record=True, print_msg=False) as l:
+        logger.debug('foo')
+    assert_equal(len(l), 1)
+    assert_in('test_logging', l[0])
+
+    with use_log_level('debug', 'foo', True, False) as l:
+        logger.debug('bar')
+    assert_equal(len(l), 0)
+
+    with use_log_level('info', record=True, print_msg=False) as l:
+        logger.debug('foo')
+        logger.info('bar')
+    assert_equal(len(l), 1)
+    assert_not_in('unknown', l[0])
diff --git a/vispy/util/tests/test_run.py b/vispy/util/tests/test_run.py
new file mode 100644
index 0000000..db43ff3
--- /dev/null
+++ b/vispy/util/tests/test_run.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+
+from nose.tools import assert_raises
+
+from vispy.util import run_subprocess
+
+
+def test_run():
+    """Test running subprocesses
+    """
+    bad_name = 'foo_nonexist_test'
+    assert_raises(Exception, run_subprocess, [bad_name])
diff --git a/vispy/util/tests/test_transforms.py b/vispy/util/tests/test_transforms.py
new file mode 100644
index 0000000..ac266ca
--- /dev/null
+++ b/vispy/util/tests/test_transforms.py
@@ -0,0 +1,35 @@
+import numpy as np
+from nose.tools import assert_equal
+from numpy.testing import assert_allclose
+
+from vispy.util.transforms import (translate, scale, xrotate, yrotate,
+                                   zrotate, rotate, ortho, frustum,
+                                   perspective)
+
+
+def test_transforms():
+    """Test basic transforms"""
+    xfm = np.random.randn(4, 4).astype(np.float32)
+
+    for rot in [xrotate, yrotate, zrotate]:
+        new_xfm = rot(rot(xfm, 90), -90)
+        assert_allclose(xfm, new_xfm)
+
+    new_xfm = rotate(rotate(xfm, 90, 1, 0, 0), 90, -1, 0, 0)
+    assert_allclose(xfm, new_xfm)
+
+    new_xfm = translate(translate(xfm, 1, -1), 1, -1, 1)
+    assert_allclose(xfm, new_xfm)
+
+    new_xfm = scale(scale(xfm, 1, 2, 3), 1, 1. / 2., 1. / 3.)
+    assert_allclose(xfm, new_xfm)
+
+    # These could be more complex...
+    xfm = ortho(-1, 1, -1, 1, -1, 1)
+    assert_equal(xfm.shape, (4, 4))
+
+    xfm = frustum(-1, 1, -1, 1, -1, 1)
+    assert_equal(xfm.shape, (4, 4))
+
+    xfm = perspective(1, 1, -1, 1)
+    assert_equal(xfm.shape, (4, 4))
diff --git a/vispy/util/tests/test_vispy.py b/vispy/util/tests/test_vispy.py
new file mode 100644
index 0000000..aec7313
--- /dev/null
+++ b/vispy/util/tests/test_vispy.py
@@ -0,0 +1,43 @@
+""" Tests to ensure that base vispy namespace functions correctly,
+including configuration options.
+"""
+
+from nose.tools import assert_raises, assert_equal, assert_not_equal
+
+import vispy.app
+from vispy.testing import requires_application
+
+
+ at requires_application('pyside')
+def test_use():
+    
+    # Set default app to None, so we can test the use function
+    vispy.app.use_app()
+    default_app = vispy.app._default_app.default_app
+    vispy.app._default_app.default_app = None
+    
+    app_name = default_app.backend_name.split(' ')[0]
+    
+    try:
+        # With no arguments, should do nothing
+        vispy.use()
+        assert_equal(vispy.app._default_app.default_app, None)
+        
+        # With only gl args, should do nothing to app
+        vispy.use(gl='desktop')
+        assert_equal(vispy.app._default_app.default_app, None)
+        
+        # Specify app (one we know works)
+        vispy.use(app_name)
+        assert_not_equal(vispy.app._default_app.default_app, None)
+        
+        # Again, but now wrong app
+        wrong_name = 'glut' if app_name.lower() != 'glut' else 'pyglet'
+        assert_raises(RuntimeError, vispy.use, wrong_name)
+        
+        # And both
+        vispy.use(app_name, 'desktop')
+    
+    finally:
+        # Restore
+        vispy.app._default_app.default_app = default_app
diff --git a/vispy/util/transforms.py b/vispy/util/transforms.py
index 79e667a..4ef6e49 100644
--- a/vispy/util/transforms.py
+++ b/vispy/util/transforms.py
@@ -2,147 +2,313 @@
 # -*- coding: utf-8 -*-
 """
 Very simple transformation library that is needed for some examples.
+
+Note that functions that take a matrix as input generally operate on that
+matrix in place.
 """
 
+# Note: we use functions (e.g. sin) from math module because they're faster
+
 import math
-import numpy
 import numpy as np
 
 
 def translate(M, x, y=None, z=None):
-    """
-    translate produces a translation by (x, y, z) . 
-    
+    """Translate by an offset (x, y, z) .
+
     Parameters
     ----------
-    x, y, z
-        Specify the x, y, and z coordinates of a translation vector.
+    M : array
+        Original transformation (4x4).
+    x : float
+        X coordinate of a translation vector.
+    y : float | None
+        Y coordinate of translation vector. If None, `x` will be used.
+    z : float | None
+        Z coordinate of translation vector. If None, `x` will be used.
+
+    Returns
+    -------
+    M : array
+        Updated transformation (4x4). Note that this function operates
+        in-place.
     """
-    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)
+    y = x if y is None else y
+    z = x if z is None else z
+    T = np.array([[1.0, 0.0, 0.0, x],
+                  [0.0, 1.0, 0.0, y],
+                  [0.0, 0.0, 1.0, z],
+                  [0.0, 0.0, 0.0, 1.0]], dtype=M.dtype).T
+    M[...] = np.dot(M, T)
+    return M
 
 
 def scale(M, x, y=None, z=None):
+    """Non-uniform scaling along the x, y, and z axes
+
+    Parameters
+    ----------
+    M : array
+        Original transformation (4x4).
+    x : float
+        X coordinate of the translation vector.
+    y : float | None
+        Y coordinate of the translation vector. If None, `x` will be used.
+    z : float | None
+        Z coordinate of the translation vector. If None, `x` will be used.
+
+    Returns
+    -------
+    M : array
+        Updated transformation (4x4). Note that this function operates
+        in-place.
     """
-    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.
+    y = x if y is None else y
+    z = x if z is None else z
+    S = np.array([[x, 0.0, 0.0, 0.0],
+                  [0.0, y, 0.0, 0.0],
+                  [0.0, 0.0, z, 0.0],
+                  [0.0, 0.0, 0.0, 1.0]], dtype=M.dtype).T
+    M[...] = np.dot(M, S)
+    return M
+
+
+def xrotate(M, theta):
+    """Rotate about the X axis
 
     Parameters
     ----------
-    x, y, z
-        Specify scale factors along the x, y, and z axes, respectively.
+    M : array
+        Original transformation (4x4).
+    theta : float
+        Specifies the angle of rotation, in degrees.
+
+    Returns
+    -------
+    M : array
+        Updated transformation (4x4). Note that this function operates
+        in-place.
     """
-    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)
+    t = math.pi * theta / 180.
+    cosT = math.cos(t)
+    sinT = math.sin(t)
+    R = np.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=M.dtype)
+    M[...] = np.dot(M, R)
+    return M
 
 
-def rotate(M, angle, x, y, z, point=None):
+def yrotate(M, theta):
+    """Rotate about the Y axis
+
+    Parameters
+    ----------
+    M : array
+        Original transformation (4x4).
+    theta : float
+        Specifies the angle of rotation, in degrees.
+
+    Returns
+    -------
+    M : array
+        Updated transformation (4x4). Note that this function operates
+        in-place.
     """
-    rotate produces a rotation of angle degrees around the vector (x, y, z).
-    
+    t = math.pi * theta / 180
+    cosT = math.cos(t)
+    sinT = math.sin(t)
+    R = np.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=M.dtype)
+    M[...] = np.dot(M, R)
+    return M
+
+
+def zrotate(M, theta):
+    """Rotate about the Z axis
+
     Parameters
     ----------
-    M
-       Current transformation as a numpy array
+    M : array
+        Original transformation (4x4).
+    theta : float
+        Specifies the angle of rotation, in degrees.
+
+    Returns
+    -------
+    M : array
+        Updated transformation (4x4). Note that this function operates
+        in-place.
+    """
+    t = math.pi * theta / 180
+    cosT = math.cos(t)
+    sinT = math.sin(t)
+    R = np.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=M.dtype)
+    M[...] = np.dot(M, R)
+    return M
+
+
+def rotate(M, angle, x, y, z, point=None):
+    """Rotation about a vector
 
-    angle
-       Specifies the angle of rotation, in degrees.
+    Parameters
+    ----------
+    M : array
+        Original transformation (4x4).
+    angle : float
+        Specifies the angle of rotation, in degrees.
+    x : float
+        X coordinate of the angle of rotation vector.
+    y : float | None
+        Y coordinate of the angle of rotation vector.
+    z : float | None
+        Z coordinate of the angle of rotation vector.
 
-    x, y, z
-        Specify the x, y, and z coordinates of a vector, respectively.
+    Returns
+    -------
+    M : array
+        Updated transformation (4x4). Note that this function operates
+        in-place.
     """
-    angle = math.pi*angle/180
-    c,s = math.cos(angle), math.sin(angle)
-    n = math.sqrt(x*x+y*y+z*z)
+    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
+    cx, cy, cz = (1 - c) * x, (1 - c) * y, (1 - c) * z
+    R = np.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]], dtype=M.dtype).T
+    M[...] = np.dot(M, R)
+    return M
+
+
+def ortho(left, right, bottom, top, znear, zfar):
+    """Create orthographic projection matrix
+
+    Parameters
+    ----------
+    left : float
+        Left coordinate of the field of view.
+    right : float
+        Right coordinate of the field of view.
+    bottom : float
+        Bottom coordinate of the field of view.
+    top : float
+        Top coordinate of the field of view.
+    znear : float
+        Near coordinate of the field of view.
+    zfar : float
+        Far coordinate of the field of view.
+
+    Returns
+    -------
+    M : array
+        Orthographic projection matrix (4x4).
+    """
+    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
+
+
+def frustum(left, right, bottom, top, znear, zfar):
+    """Create view frustum
+
+    Parameters
+    ----------
+    left : float
+        Left coordinate of the field of view.
+    right : float
+        Right coordinate of the field of view.
+    bottom : float
+        Bottom coordinate of the field of view.
+    top : float
+        Top coordinate of the field of view.
+    znear : float
+        Near coordinate of the field of view.
+    zfar : float
+        Far coordinate of the field of view.
+
+    Returns
+    -------
+    M : array
+        View frustum matrix (4x4).
+    """
+    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
+    """Create perspective projection matrix
+
+    Parameters
+    ----------
+    fovy : float
+        The field of view along the y axis.
+    aspect : float
+        Aspect ratio of the view.
+    znear : float
+        Near coordinate of the field of view.
+    zfar : float
+        Far coordinate of the field of view.
+
+    Returns
+    -------
+    M : array
+        Perspective projection matrix (4x4).
+    """
+    assert(znear != zfar)
+    h = math.tan(fovy / 360.0 * math.pi) * znear
     w = h * aspect
-    return frustum( -w, w, -h, h, znear, zfar )
+    return frustum(-w, w, -h, h, znear, zfar)
+
+
+def affine_map(points1, points2):
+    """ Find a 3D transformation matrix that maps points1 onto points2.
+    
+    Arguments are specified as arrays of four 3D coordinates, shape (4, 3).
+    """
+    A = np.ones((4, 4))
+    A[:, :3] = points1
+    B = np.ones((4, 4))
+    B[:, :3] = points2
+    
+    # solve 3 sets of linear equations to determine
+    # transformation matrix elements
+    matrix = np.eye(4)
+    for i in range(3):
+        # solve Ax = B; x is one row of the desired transformation matrix
+        matrix[i] = np.linalg.solve(A, B[:, i]) 
+    
+    return matrix
diff --git a/vispy/util/wrappers.py b/vispy/util/wrappers.py
new file mode 100644
index 0000000..a344c77
--- /dev/null
+++ b/vispy/util/wrappers.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+Some wrappers to avoid circular imports, or make certain calls easier.
+"""
+
+"""
+The idea of a 'global' vispy.use function is that although vispy.app
+and vispy.gloo.gl can be used independently, they are not complely
+independent for some configureation. E.g. when using real ES 2.0,
+the app backend should use EGL and not a desktop OpenGL context. Also,
+we probably want it to be easy to configure vispy to use the ipython
+notebook backend, which requires specifc config of both app and gl.
+
+This module does not have to be aware of the available app and gl
+backends, but it should be(come) aware of (in)compatibilities between
+them.
+"""
+
+import subprocess
+import inspect
+
+
+def use(app=None, gl=None):
+    """ Set the usage options for vispy
+
+    Specify what app backend and GL backend to use. Also see
+    ``vispy.app.use_app()`` and ``vispy.gloo.gl.use_gl()``.
+
+    Parameters
+    ----------
+    app : str
+        The app backend to use (case insensitive). Standard backends:
+            * 'PyQt4': use Qt widget toolkit via PyQt4.
+            * 'PySide': use Qt widget toolkit via PySide.
+            * 'PyGlet': use Pyglet backend.
+            * 'Glfw': use Glfw backend (successor of Glut). Widely available
+              on Linux.
+            * 'SDL2': use SDL v2 backend.
+            * 'Glut': use Glut backend. Widely available but limited.
+              Not recommended.
+        Additional backends:
+            * 'ipynb_vnc': render in the IPython notebook via a VNC approach
+              (experimental)
+    gl : str
+        The gl backend to use (case insensitive). Options are:
+            * 'desktop': use Vispy's desktop OpenGL API.
+            * 'pyopengl': use PyOpenGL's desktop OpenGL API. Mostly for
+              testing.
+            * 'angle': (TO COME) use real OpenGL ES 2.0 on Windows via Angle.
+              Availability of ES 2.0 is larger for Windows, since it relies
+              on DirectX.
+            * If 'debug' is included in this argument, vispy will check for
+              errors after each gl command.
+
+    Notes
+    -----
+    If the app option is given, ``vispy.app.use_app()`` is called. If
+    the gl option is given, ``vispy.gloo.use_gl()`` is called.
+
+    If an app backend name is provided, and that backend could not be
+    loaded, an error is raised.
+
+    If no backend name is provided, Vispy 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.
+    """
+
+    # Example for future. This wont work (yet).
+    if app == 'ipynb_webgl':
+        app = 'headless'
+        gl = 'webgl'
+
+    # Apply now
+    if app:
+        import vispy.app
+        vispy.app.use_app(app)
+    if gl:
+        import vispy.gloo
+        vispy.gloo.gl.use_gl(gl)
+
+
+# Define test proxy function, so we don't have to import vispy.testing always
+def test(label='full', coverage=False, verbosity=1, *extra_args):
+    """Test vispy software
+
+    Parameters
+    ----------
+    label : str
+        Can be one of 'full', 'nose', 'nobackend', 'extra', 'lineendings',
+        'flake', or any backend name (e.g., 'qt').
+    coverage : bool
+        Produce coverage outputs (.coverage file).
+    verbosity : int
+        Verbosity level to use when running ``nose``.
+    """
+    from ..testing import _tester
+    return _tester(label, coverage, verbosity, extra_args)
+
+
+def run_subprocess(command):
+    """Run command using subprocess.Popen
+
+    Run command and wait for command to complete. If the return code was zero
+    then return, otherwise raise CalledProcessError.
+    By default, this will also add stdout= and stderr=subproces.PIPE
+    to the call to Popen to suppress printing to the terminal.
+
+    Parameters
+    ----------
+    command : list of str
+        Command to run as subprocess (see subprocess.Popen documentation).
+
+    Returns
+    -------
+    stdout : str
+        Stdout returned by the process.
+    stderr : str
+        Stderr returned by the process.
+    """
+    # code adapted with permission from mne-python
+    kwargs = dict(stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+
+    p = subprocess.Popen(command, **kwargs)
+    stdout_, stderr = p.communicate()
+
+    output = (stdout_, stderr)
+    if p.returncode:
+        print(stdout_)
+        print(stderr)
+        err_fun = subprocess.CalledProcessError.__init__
+        if 'output' in inspect.getargspec(err_fun).args:
+            raise subprocess.CalledProcessError(p.returncode, command, output)
+        else:
+            raise subprocess.CalledProcessError(p.returncode, command)
+
+    return output
diff --git a/vispy/visuals/README b/vispy/visuals/README
deleted file mode 100644
index 8e540ef..0000000
--- a/vispy/visuals/README
+++ /dev/null
@@ -1,4 +0,0 @@
-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
deleted file mode 100644
index d5d8909..0000000
--- a/vispy/visuals/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-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
deleted file mode 100644
index 22b10d7..0000000
--- a/vispy/visuals/base.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-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
deleted file mode 100644
index 15adab1..0000000
--- a/vispy/visuals/box.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""
-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
deleted file mode 100644
index 8243708..0000000
--- a/vispy/visuals/camera.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""
-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
deleted file mode 100644
index e69de29..0000000
diff --git a/vispy/visuals/data.py b/vispy/visuals/data.py
deleted file mode 100644
index 918c870..0000000
--- a/vispy/visuals/data.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# -*- 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
deleted file mode 100644
index e69de29..0000000
diff --git a/vispy/visuals/layout.py b/vispy/visuals/layout.py
deleted file mode 100644
index b9afedc..0000000
--- a/vispy/visuals/layout.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""
-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
deleted file mode 100644
index e69de29..0000000
diff --git a/vispy/visuals/line.py b/vispy/visuals/line.py
deleted file mode 100644
index 1409d22..0000000
--- a/vispy/visuals/line.py
+++ /dev/null
@@ -1,369 +0,0 @@
-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
deleted file mode 100644
index e622500..0000000
--- a/vispy/visuals/mesh.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""
-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
deleted file mode 100644
index dcb8871..0000000
--- a/vispy/visuals/particle.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""
-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
deleted file mode 100644
index e69de29..0000000
diff --git a/vispy/visuals/view_box.py b/vispy/visuals/view_box.py
deleted file mode 100644
index 8b6d172..0000000
--- a/vispy/visuals/view_box.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""
-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
deleted file mode 100644
index b83d9dc..0000000
--- a/vispy/visuals/visual.py
+++ /dev/null
@@ -1,149 +0,0 @@
-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
deleted file mode 100644
index e69de29..0000000

-- 
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