[iortcw] 233/497: All: Rend2: VBO cleanup / Vertex array object support / Reduce redundant GL calls

Simon McVittie smcv at debian.org
Fri Sep 8 10:36:57 UTC 2017


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

smcv pushed a commit to annotated tag 1.42d
in repository iortcw.

commit 3ef5eb48138e1c97d6bd81ef382f30a2d8fc199a
Author: M4N4T4RMS at gmail.com <M4N4T4RMS at gmail.com@e65d2741-a53d-b2dc-ae96-bb75fa5e4c4a>
Date:   Wed Oct 15 00:18:51 2014 +0000

    All: Rend2: VBO cleanup / Vertex array object support / Reduce redundant GL calls
---
 MP/code/rend2/qgl.h           |  10 +
 MP/code/rend2/tr_animation.c  |   4 +-
 MP/code/rend2/tr_backend.c    | 102 +++--
 MP/code/rend2/tr_bsp.c        |  80 ++--
 MP/code/rend2/tr_cmds.c       |   5 +-
 MP/code/rend2/tr_extensions.c |  27 ++
 MP/code/rend2/tr_fbo.c        |   1 -
 MP/code/rend2/tr_glsl.c       | 291 ++-----------
 MP/code/rend2/tr_init.c       |  17 +-
 MP/code/rend2/tr_light.c      |   2 +-
 MP/code/rend2/tr_local.h      | 185 ++++----
 MP/code/rend2/tr_main.c       |   2 +-
 MP/code/rend2/tr_mesh.c       |   4 +-
 MP/code/rend2/tr_model.c      | 190 +++++---
 MP/code/rend2/tr_model_iqm.c  |   4 +-
 MP/code/rend2/tr_shade.c      |  47 +-
 MP/code/rend2/tr_shade_calc.c |  16 +-
 MP/code/rend2/tr_sky.c        |  12 +-
 MP/code/rend2/tr_surface.c    |  82 ++--
 MP/code/rend2/tr_vbo.c        | 974 +++++++++++++++---------------------------
 MP/code/rend2/tr_world.c      |   4 +-
 MP/code/renderer/qgl.h        |  10 +
 MP/rend2-readme.txt           |   4 +-
 SP/code/rend2/qgl.h           |  10 +
 SP/code/rend2/tr_animation.c  |   4 +-
 SP/code/rend2/tr_backend.c    |  99 +++--
 SP/code/rend2/tr_bsp.c        |  80 ++--
 SP/code/rend2/tr_cmds.c       |   5 +-
 SP/code/rend2/tr_extensions.c |  27 ++
 SP/code/rend2/tr_fbo.c        |   1 -
 SP/code/rend2/tr_glsl.c       | 291 ++-----------
 SP/code/rend2/tr_init.c       |  17 +-
 SP/code/rend2/tr_light.c      |   2 +-
 SP/code/rend2/tr_local.h      | 184 ++++----
 SP/code/rend2/tr_main.c       |   2 +-
 SP/code/rend2/tr_mesh.c       |   4 +-
 SP/code/rend2/tr_model.c      | 190 +++++---
 SP/code/rend2/tr_model_iqm.c  |   4 +-
 SP/code/rend2/tr_shade.c      |  47 +-
 SP/code/rend2/tr_shade_calc.c |  16 +-
 SP/code/rend2/tr_sky.c        |  12 +-
 SP/code/rend2/tr_surface.c    |  82 ++--
 SP/code/rend2/tr_vbo.c        | 974 +++++++++++++++---------------------------
 SP/code/rend2/tr_world.c      |   4 +-
 SP/code/renderer/qgl.h        |  10 +
 SP/rend2-readme.txt           |   4 +-
 46 files changed, 1672 insertions(+), 2470 deletions(-)

diff --git a/MP/code/rend2/qgl.h b/MP/code/rend2/qgl.h
index bbd7a4b..42a7618 100644
--- a/MP/code/rend2/qgl.h
+++ b/MP/code/rend2/qgl.h
@@ -467,6 +467,16 @@ extern void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
 #define GL_TEXTURE_CUBE_MAP_SEAMLESS               0x884F
 #endif
 
+// GL_ARB_vertex_array_object
+extern void (APIENTRY * qglBindVertexArrayARB)(GLuint array);
+extern void (APIENTRY * qglDeleteVertexArraysARB)(GLsizei n, const GLuint *arrays);
+extern void (APIENTRY * qglGenVertexArraysARB)(GLsizei n, GLuint *arrays);
+extern GLboolean (APIENTRY * qglIsVertexArrayARB)(GLuint array);
+#ifndef GL_ARB_vertex_array_object
+#define GL_ARB_vertex_array_object
+#define GL_VERTEX_ARRAY_BINDING_ARB                0x85B5
+#endif
+
 #if defined(WIN32)
 // WGL_ARB_create_context
 #ifndef WGL_ARB_create_context
diff --git a/MP/code/rend2/tr_animation.c b/MP/code/rend2/tr_animation.c
index e65a53e..f912366 100644
--- a/MP/code/rend2/tr_animation.c
+++ b/MP/code/rend2/tr_animation.c
@@ -1187,7 +1187,7 @@ void RB_SurfaceAnim( mdsSurface_t *surface ) {
 
 		LocalMatrixTransformVector( v->normal, bones[v->weights[0].boneIndex].matrix, newNormal );
 		
-		*tempNormal = R_VboPackNormal(newNormal);
+		*tempNormal = R_VaoPackNormal(newNormal);
 
 		tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
 		tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
@@ -1756,7 +1756,7 @@ void RB_MDRSurfaceAnim( mdrSurface_t *surface )
 		tess.xyz[baseVertex + j][1] = tempVert[1];
 		tess.xyz[baseVertex + j][2] = tempVert[2];
 
-		tess.normal[baseVertex + j] = R_VboPackNormal(tempNormal);
+		tess.normal[baseVertex + j] = R_VaoPackNormal(tempNormal);
 
 		tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
 		tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
diff --git a/MP/code/rend2/tr_backend.c b/MP/code/rend2/tr_backend.c
index d01f66a..7a6b31e 100644
--- a/MP/code/rend2/tr_backend.c
+++ b/MP/code/rend2/tr_backend.c
@@ -127,8 +127,6 @@ void GL_Cull( int cullType ) {
 		return;
 	}
 
-	glState.faceCulling = cullType;
-
 	if ( cullType == CT_TWO_SIDED ) 
 	{
 		qglDisable( GL_CULL_FACE );
@@ -136,7 +134,11 @@ void GL_Cull( int cullType ) {
 	else 
 	{
 		qboolean cullFront;
-		qglEnable( GL_CULL_FACE );
+
+		if ( glState.faceCulling == CT_TWO_SIDED )
+		{
+			qglEnable( GL_CULL_FACE );
+		}
 
 		cullFront = (cullType == CT_FRONT_SIDED);
 		if ( backEnd.viewParms.isMirror )
@@ -148,8 +150,13 @@ void GL_Cull( int cullType ) {
 			cullFront = !cullFront;
 		}
 
-		qglCullFace( cullFront ? GL_FRONT : GL_BACK );
+		if (glState.faceCullFront != cullFront)
+			qglCullFace( cullFront ? GL_FRONT : GL_BACK );
+
+		glState.faceCullFront = cullFront;
 	}
+
+	glState.faceCulling = cullType;
 }
 
 /*
@@ -219,9 +226,26 @@ void GL_State( unsigned long stateBits ) {
 	// check blend bits
 	//
 	if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) {
-		GLenum srcFactor = GL_ONE, dstFactor = GL_ONE;
+		uint32_t oldState = glState.glStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+		uint32_t newState = stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+		uint32_t storedState = glState.storedGlState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+
+		if (oldState == 0)
+ 		{
+			qglEnable( GL_BLEND );
+		}
+		else if (newState == 0)
+		{
+			qglDisable( GL_BLEND );
+		}
+
+		if (newState != 0 && storedState != newState)
+		{
+			GLenum srcFactor = GL_ONE, dstFactor = GL_ONE;
+
+			glState.storedGlState &= ~( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+			glState.storedGlState |= newState;
 
-		if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) {
 			switch ( stateBits & GLS_SRCBLEND_BITS )
 			{
 			case GLS_SRCBLEND_ZERO:
@@ -287,11 +311,7 @@ void GL_State( unsigned long stateBits ) {
 				break;
 			}
 
-			qglEnable( GL_BLEND );
 			qglBlendFunc( srcFactor, dstFactor );
-		} else
-		{
-			qglDisable( GL_BLEND );
 		}
 	}
 
@@ -335,26 +355,36 @@ void GL_State( unsigned long stateBits ) {
 	// alpha test
 	//
 	if ( diff & GLS_ATEST_BITS ) {
-		switch ( stateBits & GLS_ATEST_BITS )
+		uint32_t oldState = glState.glStateBits & GLS_ATEST_BITS;
+		uint32_t newState = stateBits & GLS_ATEST_BITS;
+		uint32_t storedState = glState.storedGlState & GLS_ATEST_BITS;
+
+		if (oldState == 0)
 		{
-		case 0:
-			qglDisable( GL_ALPHA_TEST );
-			break;
-		case GLS_ATEST_GT_0:
-			qglEnable( GL_ALPHA_TEST );
-			qglAlphaFunc( GL_GREATER, 0.0f );
-			break;
-		case GLS_ATEST_LT_80:
-			qglEnable( GL_ALPHA_TEST );
-			qglAlphaFunc( GL_LESS, 0.5f );
-			break;
-		case GLS_ATEST_GE_80:
-			qglEnable( GL_ALPHA_TEST );
-			qglAlphaFunc( GL_GEQUAL, 0.5f );
-			break;
-		default:
-			assert( 0 );
-			break;
+			qglEnable(GL_ALPHA_TEST);
+		}
+		else if (newState == 0)
+		{
+			qglDisable(GL_ALPHA_TEST);
+		}
+
+		if (newState != 0 && storedState != newState)
+		{
+			glState.storedGlState &= ~GLS_ATEST_BITS;
+			glState.storedGlState |= newState;
+
+			switch ( newState )
+			{
+			case GLS_ATEST_GT_0:
+				qglAlphaFunc( GL_GREATER, 0.0f );
+				break;
+			case GLS_ATEST_LT_80:
+				qglAlphaFunc( GL_LESS, 0.5f );
+				break;
+			case GLS_ATEST_GE_80:
+				qglAlphaFunc( GL_GEQUAL, 0.5f );
+				break;
+			}
 		}
 	}
 
@@ -392,6 +422,7 @@ static void RB_Hyperspace( void ) {
 	c = ( backEnd.refdef.time & 255 ) / 255.0f;
 	qglClearColor( c, c, c, 1 );
 	qglClear( GL_COLOR_BUFFER_BIT );
+	qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 
 	backEnd.isHyperspace = qtrue;
 }
@@ -495,7 +526,7 @@ void RB_BeginDrawingView( void ) {
 					qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] );
 				} else {
 //					qglClearColor ( 1.0, 0.0, 0.0, 1.0 );	// red clear for testing portal sky clear
-					qglClearColor( 0.5, 0.5, 0.5, 1.0 );
+//					qglClearColor( 0.5, 0.5, 0.5, 1.0 );
 				}
 			} else {                                                    // rendered sky (either clear color or draw quake sky)
 				if ( glfogsettings[FOG_PORTALVIEW].registered ) {
@@ -540,7 +571,7 @@ void RB_BeginDrawingView( void ) {
 				qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] );
 			} else {
 //				qglClearColor ( 0.0, 0.0, 1.0, 1.0 );	// blue clear for testing world sky clear
-				qglClearColor( 0.05, 0.05, 0.05, 1.0 );  // JPW NERVE changed per id req was 0.5s
+//				qglClearColor( 0.05, 0.05, 0.05, 1.0 );  // JPW NERVE changed per id req was 0.5s
 			}
 		} else {        // world scene, no portal sky, not fastsky, clear color if fog says to, otherwise, just set the clearcolor
 			if ( glfogsettings[FOG_CURRENT].registered ) { // try to clear fastsky with current fog color
@@ -553,18 +584,10 @@ void RB_BeginDrawingView( void ) {
 		}
 	}
 
-	// clear to white for shadow maps
-	if (backEnd.viewParms.flags & VPF_SHADOWMAP)
-	{
-		clearBits |= GL_COLOR_BUFFER_BIT;
-		qglClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
-	}
-
 	// clear to black for cube maps
 	if (tr.renderCubeFbo && backEnd.viewParms.targetFbo == tr.renderCubeFbo)
 	{
 		clearBits |= GL_COLOR_BUFFER_BIT;
-		qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
 	}
 
 	if ( clearBits ) {
@@ -582,6 +605,7 @@ void RB_BeginDrawingView( void ) {
 	}
 
 	glState.faceCulling = -1;       // force face culling to set next time
+	glState.faceCullFront = -1;	// same as above
 
 	// we will only draw a sun if there was sky rendered in this view
 	backEnd.skyRenderedThisView = qfalse;
diff --git a/MP/code/rend2/tr_bsp.c b/MP/code/rend2/tr_bsp.c
index 042efdc..399f2fc 100644
--- a/MP/code/rend2/tr_bsp.c
+++ b/MP/code/rend2/tr_bsp.c
@@ -2077,10 +2077,10 @@ static void CopyVert(const srfVert_t * in, srfVert_t * out)
 
 /*
 ===============
-R_CreateWorldVBOs
+R_CreateWorldVaos
 ===============
 */
-static void R_CreateWorldVBOs(void)
+static void R_CreateWorldVaos(void)
 {
 	int             i, j, k;
 
@@ -2094,8 +2094,7 @@ static void R_CreateWorldVBOs(void)
 	msurface_t   *surface, **firstSurf, **lastSurf, **currSurf;
 	msurface_t  **surfacesSorted;
 
-	VBO_t *vbo;
-	IBO_t *ibo;
+	vao_t *vao;
 
 	int maxVboSize = 4 * 1024 * 1024;
 
@@ -2211,7 +2210,7 @@ static void R_CreateWorldVBOs(void)
 	{
 		int currVboSize;
 
-		// Find range of surfaces to place in a vbo/ibo by:
+		// Find range of surfaces to place in a VAO by:
 		// - Collecting a number of surfaces which fit under maxVboSize, or
 		// - All the surfaces with a single shader which go over maxVboSize
 		currVboSize = 0;
@@ -2250,7 +2249,7 @@ static void R_CreateWorldVBOs(void)
 			numSurfaces++;
 		}
 
-		ri.Printf(PRINT_ALL, "...calculating world VBO %d ( %i verts %i tris )\n", k, numVerts, numIndexes / 3);
+		ri.Printf(PRINT_ALL, "...calculating world VAO %d ( %i verts %i tris )\n", k, numVerts, numIndexes / 3);
 
 		// create arrays
 		verts = ri.Hunk_AllocateTempMemory(numVerts * sizeof(srfVert_t));
@@ -2283,25 +2282,14 @@ static void R_CreateWorldVBOs(void)
 			}
 		}
 
-#ifdef USE_VERT_TANGENT_SPACE
-		vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", k), numVerts, verts,
-									   ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT |
-									   ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
-#else
-		vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", k), numVerts, verts,
-									   ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD |
-									   ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
-#endif
-
-		ibo = R_CreateIBO2(va("staticBspModel0_IBO %i", k), numIndexes, indexes, VBO_USAGE_STATIC);
+		vao = R_CreateVao2(va("staticBspModel%i_VAO", k), numVerts, verts, numIndexes, indexes);
 
-		// point bsp surfaces to VBO
+		// point bsp surfaces to VAO
 		for (currSurf = firstSurf; currSurf < lastSurf; currSurf++)
 		{
 			srfBspSurface_t *bspSurf = (srfBspSurface_t *) (*currSurf)->data;
 
-			bspSurf->vbo = vbo;
-			bspSurf->ibo = ibo;
+			bspSurf->vao = vao;
 		}
 
 		ri.Hunk_FreeTempMemory(indexes);
@@ -2365,7 +2353,7 @@ static void R_CreateWorldVBOs(void)
 		mergedSurf = s_worldData.mergedSurfaces;
 		for(firstSurf = lastSurf = surfacesSorted; firstSurf < surfacesSorted + numSortedSurfaces; firstSurf = lastSurf)
 		{
-			srfBspSurface_t *bspSurf, *vboSurf;
+			srfBspSurface_t *bspSurf, *vaoSurf;
 
 			for ( lastSurf++ ; lastSurf < surfacesSorted + numSortedSurfaces; lastSurf++)
 			{
@@ -2389,35 +2377,34 @@ static void R_CreateWorldVBOs(void)
 
 			bspSurf = (srfBspSurface_t *)(*firstSurf)->data;
 
-			vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
-			memset(vboSurf, 0, sizeof(*vboSurf));
-			vboSurf->surfaceType = SF_VBO_MESH;
+			vaoSurf = ri.Hunk_Alloc(sizeof(*vaoSurf), h_low);
+			memset(vaoSurf, 0, sizeof(*vaoSurf));
+			vaoSurf->surfaceType = SF_VAO_MESH;
 
-			vboSurf->vbo = bspSurf->vbo;
-			vboSurf->ibo = bspSurf->ibo;
+			vaoSurf->vao = bspSurf->vao;
 
-			vboSurf->firstIndex = bspSurf->firstIndex;
-			vboSurf->minIndex = bspSurf->minIndex;
-			vboSurf->maxIndex = bspSurf->maxIndex;
+			vaoSurf->firstIndex = bspSurf->firstIndex;
+			vaoSurf->minIndex = bspSurf->minIndex;
+			vaoSurf->maxIndex = bspSurf->maxIndex;
 
-			ClearBounds(vboSurf->cullBounds[0], vboSurf->cullBounds[1]);
+			ClearBounds(vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]);
 			for (currSurf = firstSurf; currSurf < lastSurf; currSurf++)
 			{
 				srfBspSurface_t *currBspSurf = (srfBspSurface_t *)(*currSurf)->data;
 
-				vboSurf->numVerts   += currBspSurf->numVerts;
-				vboSurf->numIndexes += currBspSurf->numIndexes;
-				vboSurf->minIndex = MIN(vboSurf->minIndex, currBspSurf->minIndex);
-				vboSurf->maxIndex = MAX(vboSurf->maxIndex, currBspSurf->maxIndex);
-				AddPointToBounds((*currSurf)->cullinfo.bounds[0], vboSurf->cullBounds[0], vboSurf->cullBounds[1]);
-				AddPointToBounds((*currSurf)->cullinfo.bounds[1], vboSurf->cullBounds[0], vboSurf->cullBounds[1]);
+				vaoSurf->numVerts   += currBspSurf->numVerts;
+				vaoSurf->numIndexes += currBspSurf->numIndexes;
+				vaoSurf->minIndex = MIN(vaoSurf->minIndex, currBspSurf->minIndex);
+				vaoSurf->maxIndex = MAX(vaoSurf->maxIndex, currBspSurf->maxIndex);
+				AddPointToBounds((*currSurf)->cullinfo.bounds[0], vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]);
+				AddPointToBounds((*currSurf)->cullinfo.bounds[1], vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]);
 			}
 
-			VectorCopy(vboSurf->cullBounds[0], mergedSurf->cullinfo.bounds[0]);
-			VectorCopy(vboSurf->cullBounds[1], mergedSurf->cullinfo.bounds[1]);
+			VectorCopy(vaoSurf->cullBounds[0], mergedSurf->cullinfo.bounds[0]);
+			VectorCopy(vaoSurf->cullBounds[1], mergedSurf->cullinfo.bounds[1]);
 
 			mergedSurf->cullinfo.type =  CULLINFO_BOX;
-			mergedSurf->data          =  (surfaceType_t *)vboSurf;
+			mergedSurf->data          =  (surfaceType_t *)vaoSurf;
 			mergedSurf->fogIndex      =  (*firstSurf)->fogIndex;
 			mergedSurf->cubemapIndex  =  (*firstSurf)->cubemapIndex;
 			mergedSurf->shader        =  (*firstSurf)->shader;
@@ -2448,7 +2435,7 @@ static void R_CreateWorldVBOs(void)
 	ri.Free(surfacesSorted);
 
 	endTime = ri.Milliseconds();
-	ri.Printf(PRINT_ALL, "world VBOs calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
+	ri.Printf(PRINT_ALL, "world VAOs calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
 }
 
 /*
@@ -2519,7 +2506,7 @@ static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) {
 //	R_InitSurfMemory();
 
 	// Two passes, allocate surfaces first, then load them full of data
-	// This ensures surfaces are close together to reduce L2 cache misses when using VBOs,
+	// This ensures surfaces are close together to reduce L2 cache misses when using VAOs,
 	// which don't actually use the verts and indexes
 	in = (void *)(fileBase + surfs->fileofs);
 	out = s_worldData.surfaces;
@@ -2641,7 +2628,7 @@ static void R_LoadSubmodels( lump_t *l ) {
 
 		if(i == 0)
 		{
-			// Add this for limiting VBO surface creation
+			// Add this for limiting VAO surface creation
 			s_worldData.numWorldSurfaces = out->numSurfaces;
 		}
 	}
@@ -3789,8 +3776,8 @@ void RE_LoadWorldMap( const char *name ) {
 		}
 	}
 
-	// create static VBOS from the world
-	R_CreateWorldVBOs();
+	// create static VAOS from the world
+	R_CreateWorldVaos();
 
 	s_worldData.dataSize = (byte *)ri.Hunk_Alloc( 0, h_low ) - startMarker;
 
@@ -3807,9 +3794,8 @@ void RE_LoadWorldMap( const char *name ) {
 
 //----(SA)	end
 
-	// make sure the VBO glState entries are safe
-	R_BindNullVBO();
-	R_BindNullIBO();
+	// make sure the VAO glState entry is safe
+	R_BindNullVao();
 
 	// Render all cubemaps
 	if (r_cubeMapping->integer && tr.numCubemaps)
diff --git a/MP/code/rend2/tr_cmds.c b/MP/code/rend2/tr_cmds.c
index 3025458..c2bbe9a 100644
--- a/MP/code/rend2/tr_cmds.c
+++ b/MP/code/rend2/tr_cmds.c
@@ -73,8 +73,8 @@ void R_PerformanceCounters( void ) {
 	}
 	else if (r_speeds->integer == 7 )
 	{
-		ri.Printf( PRINT_ALL, "VBO draws: static %i dynamic %i\nMultidraws: %i merged %i\n",
-			backEnd.pc.c_staticVboDraws, backEnd.pc.c_dynamicVboDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged );
+		ri.Printf( PRINT_ALL, "VAO draws: static %i dynamic %i\nMultidraws: %i merged %i\n",
+			backEnd.pc.c_staticVaoDraws, backEnd.pc.c_dynamicVaoDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged );
 		ri.Printf( PRINT_ALL, "GLSL binds: %i  draws: gen %i light %i fog %i dlight %i\n",
 			backEnd.pc.c_glslShaderBinds, backEnd.pc.c_genericDraws, backEnd.pc.c_lightallDraws, backEnd.pc.c_fogDraws, backEnd.pc.c_dlightDraws);
 	}
@@ -519,7 +519,6 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
 				backEnd.colorMask[1] = GL_FALSE;
 				backEnd.colorMask[2] = GL_FALSE;
 				backEnd.colorMask[3] = GL_FALSE;
-				qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 
 				if (glRefConfig.framebufferObject)
 				{
diff --git a/MP/code/rend2/tr_extensions.c b/MP/code/rend2/tr_extensions.c
index 168e1e7..6ac8f83 100644
--- a/MP/code/rend2/tr_extensions.c
+++ b/MP/code/rend2/tr_extensions.c
@@ -178,6 +178,12 @@ void (APIENTRY * qglRenderbufferStorageMultisampleEXT)(GLenum target, GLsizei sa
 // GL_ARB_draw_buffers
 void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
 
+// GL_ARB_vertex_array_object
+void (APIENTRY * qglBindVertexArrayARB)(GLuint array);
+void (APIENTRY * qglDeleteVertexArraysARB)(GLsizei n, const GLuint *arrays);
+void (APIENTRY * qglGenVertexArraysARB)(GLsizei n, GLuint *arrays);
+GLboolean (APIENTRY * qglIsVertexArrayARB)(GLuint array);
+
 static qboolean GLimp_HaveExtension(const char *ext)
 {
 	const char *ptr = Q_stristr( (char *)qglGetString(GL_EXTENSIONS), ext );
@@ -682,4 +688,25 @@ void GLimp_InitExtraExtensions()
 
 	// use float lightmaps?
 	glRefConfig.floatLightmap = (glRefConfig.textureFloat && glRefConfig.halfFloatPixel && r_floatLightmap->integer && r_hdr->integer);
+
+	// GL_ARB_vertex_array_object
+	extension = "GL_ARB_vertex_array_object";
+	glRefConfig.vertexArrayObject = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglBindVertexArrayARB = (void *) SDL_GL_GetProcAddress("glBindVertexArray");
+		qglDeleteVertexArraysARB = (void *) SDL_GL_GetProcAddress("glDeleteVertexArrays");
+		qglGenVertexArraysARB = (void *) SDL_GL_GetProcAddress("glGenVertexArrays");
+		qglIsVertexArrayARB = (void *) SDL_GL_GetProcAddress("glIsVertexArray");
+
+		if (r_arb_vertex_array_object->integer)
+			glRefConfig.vertexArrayObject = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.vertexArrayObject ? 1 : 0], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
 }
diff --git a/MP/code/rend2/tr_fbo.c b/MP/code/rend2/tr_fbo.c
index 4070c6e..ba145e6 100644
--- a/MP/code/rend2/tr_fbo.c
+++ b/MP/code/rend2/tr_fbo.c
@@ -447,7 +447,6 @@ void FBO_Init(void)
 	if (tr.renderFbo)
 	{
 		FBO_Bind(tr.renderFbo);
-		qglClearColor( 1, 0, 0.5, 1 );
 		qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
 		FBO_Bind(NULL);
 	}
diff --git a/MP/code/rend2/tr_glsl.c b/MP/code/rend2/tr_glsl.c
index 00d0c19..26f5c58 100644
--- a/MP/code/rend2/tr_glsl.c
+++ b/MP/code/rend2/tr_glsl.c
@@ -537,10 +537,10 @@ static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_POSITION, "attr_Position");
 
 	if(attribs & ATTR_TEXCOORD)
-		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD0, "attr_TexCoord0");
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD, "attr_TexCoord0");
 
 	if(attribs & ATTR_LIGHTCOORD)
-		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD1, "attr_TexCoord1");
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_LIGHTCOORD, "attr_TexCoord1");
 
 //  if(attribs & ATTR_TEXCOORD2)
 //      qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2");
@@ -548,10 +548,8 @@ static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int
 //  if(attribs & ATTR_TEXCOORD3)
 //      qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD3, "attr_TexCoord3");
 
-#ifdef USE_VERT_TANGENT_SPACE
 	if(attribs & ATTR_TANGENT)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT, "attr_Tangent");
-#endif
 
 	if(attribs & ATTR_NORMAL)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL, "attr_Normal");
@@ -571,10 +569,8 @@ static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int
 	if(attribs & ATTR_NORMAL2)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL2, "attr_Normal2");
 
-#ifdef USE_VERT_TANGENT_SPACE
 	if(attribs & ATTR_TANGENT2)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT2, "attr_Tangent2");
-#endif
 
 	GLSL_LinkProgram(program->program);
 
@@ -1424,20 +1420,9 @@ void GLSL_ShutdownGPUShaders(void)
 
 	ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n");
 
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
-#ifdef USE_VERT_TANGENT_SPACE
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
-#endif
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
-#ifdef USE_VERT_TANGENT_SPACE
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
-#endif
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
+	for (i = 0; i < ATTR_INDEX_COUNT; i++)
+		qglDisableVertexAttribArrayARB(i);
+
 	GLSL_BindNullProgram();
 
 	for ( i = 0; i < GENERICDEF_COUNT; i++)
@@ -1512,271 +1497,59 @@ void GLSL_BindNullProgram(void)
 }
 
 
-void GLSL_VertexAttribsState(uint32_t stateBits)
-{
-	uint32_t		diff;
-
-	diff = stateBits ^ glState.vertexAttribsState;
-
-	if(diff)
-	{
-		if(diff & ATTR_POSITION)
-		{
-			if(stateBits & ATTR_POSITION)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION);
-			}
-		}
-
-		if(diff & ATTR_TEXCOORD)
-		{
-			if(stateBits & ATTR_TEXCOORD)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
-			}
-		}
-
-		if(diff & ATTR_LIGHTCOORD)
-		{
-			if(stateBits & ATTR_LIGHTCOORD)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
-			}
-		}
-
-		if(diff & ATTR_NORMAL)
-		{
-			if(stateBits & ATTR_NORMAL)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
-			}
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(diff & ATTR_TANGENT)
-		{
-			if(stateBits & ATTR_TANGENT)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
-			}
-		}
-#endif
-
-		if(diff & ATTR_COLOR)
-		{
-			if(stateBits & ATTR_COLOR)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_COLOR);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR);
-			}
-		}
-
-		if(diff & ATTR_LIGHTDIRECTION)
-		{
-			if(stateBits & ATTR_LIGHTDIRECTION)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
-			}
-		}
-
-		if(diff & ATTR_POSITION2)
-		{
-			if(stateBits & ATTR_POSITION2)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
-			}
-		}
-
-		if(diff & ATTR_NORMAL2)
-		{
-			if(stateBits & ATTR_NORMAL2)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
-			}
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(diff & ATTR_TANGENT2)
-		{
-			if(stateBits & ATTR_TANGENT2)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
-			}
-		}
-#endif
-	}
-
-	GLSL_VertexAttribPointers(stateBits);
-
-	glState.vertexAttribsState = stateBits;
-}
-
 void GLSL_VertexAttribPointers(uint32_t attribBits)
 {
-	qboolean animated;
 	int newFrame, oldFrame;
-	VBO_t *vbo = glState.currentVBO;
-	
-	if(!vbo)
+	vao_t *vao = glState.currentVao;
+	int attribIndex;
+	uint32_t extraOffsets[ATTR_INDEX_COUNT];
+
+	if(!vao)
 	{
-		ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VBO bound");
+		ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VAO bound");
 		return;
 	}
 
 	// don't just call LogComment, or we will get a call to va() every frame!
 	if(r_logFile->integer)
 	{
-		GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", vbo->name));
+		GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", vao->name));
 	}
 
+	for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
+		extraOffsets[attribIndex] = 0;
+
 	// position/normal/tangent are always set in case of animation
 	oldFrame = glState.vertexAttribsOldFrame;
 	newFrame = glState.vertexAttribsNewFrame;
-	animated = glState.vertexAnimation;
-	
-	if((attribBits & ATTR_POSITION) && (!(glState.vertexAttribPointersSet & ATTR_POSITION) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, vbo->stride_xyz, BUFFER_OFFSET(vbo->ofs_xyz + newFrame * vbo->size_xyz));
-		glState.vertexAttribPointersSet |= ATTR_POSITION;
-	}
-
-	if((attribBits & ATTR_TEXCOORD) && !(glState.vertexAttribPointersSet & ATTR_TEXCOORD))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD0, 2, GL_FLOAT, 0, vbo->stride_st, BUFFER_OFFSET(vbo->ofs_st));
-		glState.vertexAttribPointersSet |= ATTR_TEXCOORD;
-	}
-
-	if((attribBits & ATTR_LIGHTCOORD) && !(glState.vertexAttribPointersSet & ATTR_LIGHTCOORD))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTCOORD )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD1, 2, GL_FLOAT, 0, vbo->stride_lightmap, BUFFER_OFFSET(vbo->ofs_lightmap));
-		glState.vertexAttribPointersSet |= ATTR_LIGHTCOORD;
-	}
-
-	if((attribBits & ATTR_NORMAL) && (!(glState.vertexAttribPointersSet & ATTR_NORMAL) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_NORMAL, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_normal, BUFFER_OFFSET(vbo->ofs_normal + newFrame * vbo->size_normal));
-		glState.vertexAttribPointersSet |= ATTR_NORMAL;
-	}
-
-#ifdef USE_VERT_TANGENT_SPACE
-	if((attribBits & ATTR_TANGENT) && (!(glState.vertexAttribPointersSet & ATTR_TANGENT) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_TANGENT, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_tangent, BUFFER_OFFSET(vbo->ofs_tangent + newFrame * vbo->size_normal)); // FIXME
-		glState.vertexAttribPointersSet |= ATTR_TANGENT;
-	}
-#endif
-
-	if((attribBits & ATTR_COLOR) && !(glState.vertexAttribPointersSet & ATTR_COLOR))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_COLOR )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_COLOR, 4, GL_FLOAT, 0, vbo->stride_vertexcolor, BUFFER_OFFSET(vbo->ofs_vertexcolor));
-		glState.vertexAttribPointersSet |= ATTR_COLOR;
-	}
-
-	if((attribBits & ATTR_LIGHTDIRECTION) && !(glState.vertexAttribPointersSet & ATTR_LIGHTDIRECTION))
+	if (glState.vertexAnimation)
 	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTDIRECTION )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_LIGHTDIRECTION, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_lightdir, BUFFER_OFFSET(vbo->ofs_lightdir));
-		glState.vertexAttribPointersSet |= ATTR_LIGHTDIRECTION;
+		extraOffsets[ATTR_INDEX_POSITION]  = newFrame * vao->size_xyz;
+		extraOffsets[ATTR_INDEX_POSITION2] = oldFrame * vao->size_xyz;
+		extraOffsets[ATTR_INDEX_NORMAL]    = newFrame * vao->size_normal;
+		extraOffsets[ATTR_INDEX_NORMAL2]   = oldFrame * vao->size_normal;
+		extraOffsets[ATTR_INDEX_TANGENT]   = newFrame * vao->size_normal;
+		extraOffsets[ATTR_INDEX_TANGENT2]  = oldFrame * vao->size_normal;
 	}
 
-	if((attribBits & ATTR_POSITION2) && (!(glState.vertexAttribPointersSet & ATTR_POSITION2) || animated))
+	// this may not be bound if we're using VAOs
+	if (glRefConfig.vertexArrayObject)
 	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION2 )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_POSITION2, 3, GL_FLOAT, 0, vbo->stride_xyz, BUFFER_OFFSET(vbo->ofs_xyz + oldFrame * vbo->size_xyz));
-		glState.vertexAttribPointersSet |= ATTR_POSITION2;
+		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
 	}
 
-	if((attribBits & ATTR_NORMAL2) && (!(glState.vertexAttribPointersSet & ATTR_NORMAL2) || animated))
+	for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
 	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL2 )\n");
+		uint32_t attribBit = 1 << attribIndex;
+		vaoAttrib_t *vAtb;
 
-		qglVertexAttribPointerARB(ATTR_INDEX_NORMAL2, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_normal, BUFFER_OFFSET(vbo->ofs_normal + oldFrame * vbo->size_normal));
-		glState.vertexAttribPointersSet |= ATTR_NORMAL2;
-	}
+		if (!(attribBits & attribBit))
+			continue;
 
-#ifdef USE_VERT_TANGENT_SPACE
-	if((attribBits & ATTR_TANGENT2) && (!(glState.vertexAttribPointersSet & ATTR_TANGENT2) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT2 )\n");
+		vAtb = &vao->attribs[attribIndex];
 
-		qglVertexAttribPointerARB(ATTR_INDEX_TANGENT2, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_tangent, BUFFER_OFFSET(vbo->ofs_tangent + oldFrame * vbo->size_normal)); // FIXME
-		glState.vertexAttribPointersSet |= ATTR_TANGENT2;
+		qglVertexAttribPointerARB(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + extraOffsets[attribIndex]));
 	}
-#endif
-
 }
 
 
diff --git a/MP/code/rend2/tr_init.c b/MP/code/rend2/tr_init.c
index 646a0b4..8da6300 100644
--- a/MP/code/rend2/tr_init.c
+++ b/MP/code/rend2/tr_init.c
@@ -124,6 +124,7 @@ cvar_t  *r_arb_half_float_pixel;
 cvar_t  *r_ext_framebuffer_multisample;
 cvar_t  *r_arb_seamless_cube_map;
 cvar_t  *r_arb_vertex_type_2_10_10_10_rev;
+cvar_t  *r_arb_vertex_array_object;
 
 cvar_t  *r_mergeMultidraws;
 cvar_t  *r_mergeLeafSurfaces;
@@ -1017,16 +1018,14 @@ void GL_SetDefaultState( void ) {
 	// make sure our GL state vector is set correctly
 	//
 	glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE;
+	glState.storedGlState = 0;
 
-	glState.vertexAttribsState = 0;
-	glState.vertexAttribPointersSet = 0;
 	glState.currentProgram = 0;
 	qglUseProgramObjectARB(0);
 
 	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
 	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-	glState.currentVBO = NULL;
-	glState.currentIBO = NULL;
+	glState.currentVao = NULL;
 
 	qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
 	qglDepthMask( GL_TRUE );
@@ -1065,6 +1064,11 @@ void GL_SetDefaultState( void ) {
 	}
 
 //----(SA)	end
+
+	// GL_POLYGON_OFFSET_FILL will be glEnable()d when this is used
+	qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
+
+	qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );	// FIXME: get color of sky
 }
 
 /*
@@ -1250,6 +1254,7 @@ void R_Register( void ) {
 	r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH);
 	r_arb_seamless_cube_map = ri.Cvar_Get( "r_arb_seamless_cube_map", "0", CVAR_ARCHIVE | CVAR_LATCH);
 	r_arb_vertex_type_2_10_10_10_rev = ri.Cvar_Get( "r_arb_vertex_type_2_10_10_10_rev", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_arb_vertex_array_object = ri.Cvar_Get( "r_arb_vertex_array_object", "1", CVAR_ARCHIVE | CVAR_LATCH);
 
 	r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic",
 			"0", CVAR_ARCHIVE | CVAR_LATCH );
@@ -1586,7 +1591,7 @@ void R_Init( void ) {
 
 	GLSL_InitGPUShaders();
 
-	R_InitVBOs();
+	R_InitVaos();
 
 	R_InitShaders();
 
@@ -1638,7 +1643,7 @@ void RE_Shutdown( qboolean destroyWindow ) {
 		if (glRefConfig.framebufferObject)
 			FBO_Shutdown();
 		R_DeleteTextures();
-		R_ShutdownVBOs();
+		R_ShutdownVaos();
 		GLSL_ShutdownGPUShaders();
 	}
 
diff --git a/MP/code/rend2/tr_light.c b/MP/code/rend2/tr_light.c
index e08244b..09240d1 100644
--- a/MP/code/rend2/tr_light.c
+++ b/MP/code/rend2/tr_light.c
@@ -114,7 +114,7 @@ void R_DlightBmodel( bmodel_t *bmodel ) {
 			case SF_FACE:
 			case SF_GRID:
 			case SF_TRIANGLES:
-			case SF_VBO_MESH:
+			case SF_VAO_MESH:
 				((srfBspSurface_t *)surf->data)->dlightBits = mask;
 				break;
 
diff --git a/MP/code/rend2/tr_local.h b/MP/code/rend2/tr_local.h
index fa6c2c8..fbdfc7d 100644
--- a/MP/code/rend2/tr_local.h
+++ b/MP/code/rend2/tr_local.h
@@ -56,8 +56,7 @@ typedef unsigned int glIndex_t;
 
 #define	MAX_FBOS      64
 #define MAX_VISCOUNTS 5
-#define MAX_VBOS      4096
-#define MAX_IBOS      4096
+#define MAX_VAOS      4096
 
 #define MAX_CALC_PSHADOWS    64
 #define MAX_DRAWN_PSHADOWS    16 // do not increase past 32, because bit flags are used on surfaces
@@ -67,6 +66,7 @@ typedef unsigned int glIndex_t;
  
 #define USE_VERT_TANGENT_SPACE
 
+
 // a trRefEntity_t has all the information passed in by
 // the client game, as well as some locally derived info
 typedef struct {
@@ -133,50 +133,42 @@ typedef struct image_s {
 	struct image_s* next;
 } image_t;
 
+// Ensure this is >= the ATTR_INDEX_COUNT enum below
+#define VAO_MAX_ATTRIBS 16
+
 typedef enum
 {
-	VBO_USAGE_STATIC,
-	VBO_USAGE_DYNAMIC
-} vboUsage_t;
+	VAO_USAGE_STATIC,
+	VAO_USAGE_DYNAMIC
+} vaoUsage_t;
+
+typedef struct vaoAttrib_s
+{
+	uint32_t enabled;
+	uint32_t count;
+	uint32_t type;
+	uint32_t normalized;
+	uint32_t stride;
+	uint32_t offset;
+}
+vaoAttrib_t;
 
-typedef struct VBO_s
+typedef struct vao_s
 {
 	char            name[MAX_QPATH];
 
+	uint32_t        vao;
+
 	uint32_t        vertexesVBO;
 	int             vertexesSize;	// amount of memory data allocated for all vertices in bytes
-	uint32_t        ofs_xyz;
-	uint32_t        ofs_normal;
-	uint32_t        ofs_st;
-	uint32_t        ofs_lightmap;
-	uint32_t        ofs_vertexcolor;
-	uint32_t        ofs_lightdir;
-#ifdef USE_VERT_TANGENT_SPACE
-	uint32_t        ofs_tangent;
-#endif
-	uint32_t        stride_xyz;
-	uint32_t        stride_normal;
-	uint32_t        stride_st;
-	uint32_t        stride_lightmap;
-	uint32_t        stride_vertexcolor;
-	uint32_t        stride_lightdir;
-#ifdef USE_VERT_TANGENT_SPACE
-	uint32_t        stride_tangent;
-#endif
+	vaoAttrib_t     attribs[VAO_MAX_ATTRIBS];
+
 	uint32_t        size_xyz;
 	uint32_t        size_normal;
 
-	int             attribs;
-} VBO_t;
-
-typedef struct IBO_s
-{
-	char            name[MAX_QPATH];
-
-	uint32_t        indexesVBO;
+	uint32_t        indexesIBO;
 	int             indexesSize;	// amount of memory data allocated for all triangles in bytes
-//  uint32_t        ofsIndexes;
-} IBO_t;
+} vao_t;
 
 //===============================================================================
 
@@ -597,8 +589,8 @@ typedef struct dlight_s {
 enum
 {
 	ATTR_INDEX_POSITION       = 0,
-	ATTR_INDEX_TEXCOORD0      = 1,
-	ATTR_INDEX_TEXCOORD1      = 2,
+	ATTR_INDEX_TEXCOORD       = 1,
+	ATTR_INDEX_LIGHTCOORD     = 2,
 	ATTR_INDEX_TANGENT        = 3,
 	ATTR_INDEX_NORMAL         = 4,
 	ATTR_INDEX_COLOR          = 5,
@@ -610,26 +602,28 @@ enum
 	// GPU vertex animations
 	ATTR_INDEX_POSITION2      = 10,
 	ATTR_INDEX_TANGENT2       = 11,
-	ATTR_INDEX_NORMAL2        = 12
+	ATTR_INDEX_NORMAL2        = 12,
+	
+	ATTR_INDEX_COUNT          = 13
 };
 
 enum
 {
-	ATTR_POSITION =       0x0001,
-	ATTR_TEXCOORD =       0x0002,
-	ATTR_LIGHTCOORD =     0x0004,
-	ATTR_TANGENT =        0x0008,
-	ATTR_NORMAL =         0x0010,
-	ATTR_COLOR =          0x0020,
-	ATTR_PAINTCOLOR =     0x0040,
-	ATTR_LIGHTDIRECTION = 0x0080,
-	ATTR_BONE_INDEXES =   0x0100,
-	ATTR_BONE_WEIGHTS =   0x0200,
+	ATTR_POSITION =       1 << ATTR_INDEX_POSITION,
+	ATTR_TEXCOORD =       1 << ATTR_INDEX_TEXCOORD,
+	ATTR_LIGHTCOORD =     1 << ATTR_INDEX_LIGHTCOORD,
+	ATTR_TANGENT =        1 << ATTR_INDEX_TANGENT,
+	ATTR_NORMAL =         1 << ATTR_INDEX_NORMAL,
+	ATTR_COLOR =          1 << ATTR_INDEX_COLOR,
+	ATTR_PAINTCOLOR =     1 << ATTR_INDEX_PAINTCOLOR,
+	ATTR_LIGHTDIRECTION = 1 << ATTR_INDEX_LIGHTDIRECTION,
+	ATTR_BONE_INDEXES =   1 << ATTR_INDEX_BONE_INDEXES,
+	ATTR_BONE_WEIGHTS =   1 << ATTR_INDEX_BONE_WEIGHTS,
 
 	// for .md3 interpolation
-	ATTR_POSITION2 =      0x0400,
-	ATTR_TANGENT2 =       0x0800,
-	ATTR_NORMAL2 =        0x1000,
+	ATTR_POSITION2 =      1 << ATTR_INDEX_POSITION2,
+	ATTR_TANGENT2 =       1 << ATTR_INDEX_TANGENT2,
+	ATTR_NORMAL2 =        1 << ATTR_INDEX_NORMAL2,
 
 	ATTR_DEFAULT = ATTR_POSITION,
 	ATTR_BITS =	ATTR_POSITION |
@@ -970,8 +964,8 @@ typedef enum {
 	SF_FLARE,
 	SF_ENTITY,              // beams, rails, lightning, etc that can be determined by entity
 	SF_DISPLAY_LIST,
-	SF_VBO_MESH,
-	SF_VBO_MDVMESH,
+	SF_VAO_MESH,
+	SF_VAO_MDVMESH,
 
 	SF_NUM_SURFACE_TYPES,
 	SF_MAX = 0xffffffff         // ensures that sizeof( surfaceType_t ) == sizeof( int )
@@ -1034,7 +1028,7 @@ typedef struct
 #define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0},  {0, 0, 0, 0}}
 #endif
 
-// srfBspSurface_t covers SF_GRID, SF_TRIANGLES, SF_POLY, and SF_VBO_MESH
+// srfBspSurface_t covers SF_GRID, SF_TRIANGLES, SF_POLY, and SF_VAO_MESH
 typedef struct srfBspSurface_s
 {
 	surfaceType_t surfaceType;
@@ -1064,8 +1058,7 @@ typedef struct srfBspSurface_s
 	glIndex_t       maxIndex;
 
 	// static render data
-	VBO_t          *vbo;
-	IBO_t          *ibo;
+	vao_t          *vao;
 
 	// SF_GRID specific variables after here
 
@@ -1127,7 +1120,7 @@ typedef struct srfIQModel_s {
 	int		first_triangle, num_triangles;
 } srfIQModel_t;
 
-typedef struct srfVBOMDVMesh_s
+typedef struct srfVaoMdvMesh_s
 {
 	surfaceType_t   surfaceType;
 
@@ -1141,9 +1134,8 @@ typedef struct srfVBOMDVMesh_s
 	glIndex_t       maxIndex;
 
 	// static render data
-	VBO_t          *vbo;
-	IBO_t          *ibo;
-} srfVBOMDVMesh_t;
+	vao_t          *vao;
+} srfVaoMdvMesh_t;
 
 extern void( *rb_surfaceTable[SF_NUM_SURFACE_TYPES] ) ( void * );
 
@@ -1365,8 +1357,8 @@ typedef struct mdvModel_s
 	int             numSurfaces;
 	mdvSurface_t   *surfaces;
 
-	int             numVBOSurfaces;
-	srfVBOMDVMesh_t  *vboSurfaces;
+	int             numVaoSurfaces;
+	srfVaoMdvMesh_t  *vaoSurfaces;
 
 	int             numSkins;
 } mdvModel_t;
@@ -1499,17 +1491,16 @@ typedef struct {
 	qboolean finishCalled;
 	int texEnv[2];
 	int faceCulling;
-	unsigned long glStateBits;
-	uint32_t		vertexAttribsState;
-	uint32_t		vertexAttribPointersSet;
+	int         faceCullFront;
+	uint32_t    glStateBits;
+	uint32_t    storedGlState;
 	uint32_t        vertexAttribsNewFrame;
 	uint32_t        vertexAttribsOldFrame;
 	float           vertexAttribsInterpolation;
 	qboolean        vertexAnimation;
 	shaderProgram_t *currentProgram;
 	FBO_t          *currentFBO;
-	VBO_t          *currentVBO;
-	IBO_t          *currentIBO;
+	vao_t          *currentVao;
 	mat4_t        modelview;
 	mat4_t        projection;
 	mat4_t		modelviewProjection;
@@ -1558,6 +1549,7 @@ typedef struct {
 	GLenum packedNormalDataType;
 
 	qboolean floatLightmap;
+	qboolean vertexArrayObject;
 } glRefConfig_t;
 
 typedef struct {
@@ -1565,13 +1557,12 @@ typedef struct {
 	int     c_surfBatches;
 	float c_overDraw;
 
-	int		c_vboVertexBuffers;
-	int		c_vboIndexBuffers;
-	int		c_vboVertexes;
-	int		c_vboIndexes;
+	int		c_vaoBinds;
+	int		c_vaoVertexes;
+	int		c_vaoIndexes;
 
-	int     c_staticVboDraws;
-	int     c_dynamicVboDraws;
+	int     c_staticVaoDraws;
+	int     c_dynamicVaoDraws;
 
 	int     c_multidraws;
 	int     c_multidrawsMerged;
@@ -1783,14 +1774,11 @@ typedef struct {
 	int						numFBOs;
 	FBO_t					*fbos[MAX_FBOS];
 
-	int						numVBOs;
-	VBO_t					*vbos[MAX_VBOS];
-
-	int						numIBOs;
-	IBO_t					*ibos[MAX_IBOS];
+	int						numVaos;
+	vao_t					*vaos[MAX_VAOS];
 
 	// Ridah
-	int numCacheImages;
+	//int numCacheImages;
 
 	// shader indexes from other modules will be looked up in tr.shaders[]
 	// shader indexes from drawsurfs will be looked up in sortedShaders[]
@@ -1910,6 +1898,7 @@ extern  cvar_t  *r_arb_half_float_pixel;
 extern  cvar_t  *r_ext_framebuffer_multisample;
 extern  cvar_t  *r_arb_seamless_cube_map;
 extern  cvar_t  *r_arb_vertex_type_2_10_10_10_rev;
+extern  cvar_t  *r_arb_vertex_array_object;
 
 //----(SA)	added
 extern cvar_t   *r_ext_NV_fog_dist;
@@ -2270,9 +2259,9 @@ typedef struct shaderCommands_s
 	uint32_t    lightdir[SHADER_MAX_VERTEXES] QALIGN(16);
 	//int			vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16);
 
-	VBO_t       *vbo;
-	IBO_t       *ibo;
-	qboolean    useInternalVBO;
+	void *attribPointers[ATTR_INDEX_COUNT];
+	vao_t       *vao;
+	qboolean    useInternalVao;
 
 	stageVars_t	svars QALIGN(16);
 
@@ -2313,7 +2302,7 @@ void RB_EndSurface( void );
 void RB_CheckOverflow( int verts, int indexes );
 #define RB_CHECKOVERFLOW( v,i ) if ( tess.numVertexes + ( v ) >= SHADER_MAX_VERTEXES || tess.numIndexes + ( i ) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow( v,i );}
 
-void R_DrawElementsVBO( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex );
+void R_DrawElementsVao( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex );
 void RB_StageIteratorGeneric( void );
 void RB_StageIteratorSky( void );
 void RB_StageIteratorVertexLitTexture( void );
@@ -2432,29 +2421,24 @@ VERTEX BUFFER OBJECTS
 ============================================================
 */
 
-uint32_t R_VboPackTangent(vec4_t v);
-uint32_t R_VboPackNormal(vec3_t v);
-void R_VboUnpackTangent(vec4_t v, uint32_t b);
-void R_VboUnpackNormal(vec3_t v, uint32_t b);
-
-VBO_t          *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage);
-VBO_t          *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * vertexes, uint32_t stateBits, vboUsage_t usage);
-
-IBO_t          *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage);
-IBO_t          *R_CreateIBO2(const char *name, int numIndexes, glIndex_t * inIndexes, vboUsage_t usage);
+uint32_t R_VaoPackTangent(vec4_t v);
+uint32_t R_VaoPackNormal(vec3_t v);
+void R_VaoUnpackTangent(vec4_t v, uint32_t b);
+void R_VaoUnpackNormal(vec3_t v, uint32_t b);
 
-void            R_BindVBO(VBO_t * vbo);
-void            R_BindNullVBO(void);
+vao_t          *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage);
+vao_t          *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *inIndexes);
 
-void            R_BindIBO(IBO_t * ibo);
-void            R_BindNullIBO(void);
+void            R_BindVao(vao_t *vao);
+void            R_BindNullVao(void);
 
-void            R_InitVBOs(void);
-void            R_ShutdownVBOs(void);
-void            R_VBOList_f(void);
+void Vao_SetVertexPointers(vao_t *vao);
 
-void            RB_UpdateVBOs(unsigned int attribBits);
+void            R_InitVaos(void);
+void            R_ShutdownVaos(void);
+void            R_VaoList_f(void);
 
+void            RB_UpdateTessVao(unsigned int attribBits);
 
 /*
 ============================================================
@@ -2466,7 +2450,6 @@ GLSL
 
 void GLSL_InitGPUShaders(void);
 void GLSL_ShutdownGPUShaders(void);
-void GLSL_VertexAttribsState(uint32_t stateBits);
 void GLSL_VertexAttribPointers(uint32_t attribBits);
 void GLSL_BindProgram(shaderProgram_t * program);
 void GLSL_BindNullProgram(void);
diff --git a/MP/code/rend2/tr_main.c b/MP/code/rend2/tr_main.c
index 2c6798d..3e35fd8 100644
--- a/MP/code/rend2/tr_main.c
+++ b/MP/code/rend2/tr_main.c
@@ -1834,7 +1834,7 @@ static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128
 			shortest = len;
 		}
 
-		R_VboUnpackNormal(tNormal, tess.normal[tess.indexes[i]]);
+		R_VaoUnpackNormal(tNormal, tess.normal[tess.indexes[i]]);
 
 		if ( DotProduct( normal, tNormal ) >= 0 )
  		{
diff --git a/MP/code/rend2/tr_mesh.c b/MP/code/rend2/tr_mesh.c
index be62b62..f61968c 100644
--- a/MP/code/rend2/tr_mesh.c
+++ b/MP/code/rend2/tr_mesh.c
@@ -425,9 +425,9 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
 		// don't add third_person objects if not viewing through a portal
 		if(!personalModel)
 		{
-			srfVBOMDVMesh_t *vboSurface = &model->vboSurfaces[i];
+			srfVaoMdvMesh_t *vaoSurface = &model->vaoSurfaces[i];
 
-			R_AddDrawSurf((void *)vboSurface, shader, fogNum, qfalse, qfalse, cubemapIndex );
+			R_AddDrawSurf((void *)vaoSurface, shader, fogNum, qfalse, qfalse, cubemapIndex );
 		}
 
 		surface++;
diff --git a/MP/code/rend2/tr_model.c b/MP/code/rend2/tr_model.c
index d74d1bf..a4c04d2 100644
--- a/MP/code/rend2/tr_model.c
+++ b/MP/code/rend2/tr_model.c
@@ -923,14 +923,14 @@ static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *modN
 	}
 
 	{
-		srfVBOMDVMesh_t *vboSurf;
+		srfVaoMdvMesh_t *vaoSurf;
 
-		mdvModel->numVBOSurfaces = mdvModel->numSurfaces;
-		mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low);
+		mdvModel->numVaoSurfaces = mdvModel->numSurfaces;
+		mdvModel->vaoSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vaoSurfaces) * mdvModel->numSurfaces, h_low);
 
-		vboSurf = mdvModel->vboSurfaces;
+		vaoSurf = mdvModel->vaoSurfaces;
 		surf = mdvModel->surfaces;
-		for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++)
+		for (i = 0; i < mdvModel->numSurfaces; i++, vaoSurf++, surf++)
 		{
 			vec3_t *verts;
 			vec2_t *texcoords;
@@ -980,13 +980,13 @@ static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *modN
 
 				VectorCopy(v->xyz,       verts[j]);
 
-				normals[j] = R_VboPackNormal(v->normal);
+				normals[j] = R_VaoPackNormal(v->normal);
 #ifdef USE_VERT_TANGENT_SPACE
 				CrossProduct(v->normal, v->tangent, nxt);
 				VectorCopy(v->tangent, tangent);
 				tangent[3] = (DotProduct(nxt, v->bitangent) < 0.0f) ? -1.0f : 1.0f;
 
-				tangents[j] = R_VboPackTangent(tangent);
+				tangents[j] = R_VaoPackTangent(tangent);
 #endif
 			}
 
@@ -996,37 +996,76 @@ static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *modN
 				texcoords[j][1] = st->st[1];
 			}
 
-			vboSurf->surfaceType = SF_VBO_MDVMESH;
-			vboSurf->mdvModel = mdvModel;
-			vboSurf->mdvSurface = surf;
-			vboSurf->numIndexes = surf->numIndexes;
-			vboSurf->numVerts = surf->numVerts;
-			
-			vboSurf->minIndex = 0;
-			vboSurf->maxIndex = surf->numVerts;
+			vaoSurf->surfaceType = SF_VAO_MDVMESH;
+			vaoSurf->mdvModel = mdvModel;
+			vaoSurf->mdvSurface = surf;
+			vaoSurf->numIndexes = surf->numIndexes;
+			vaoSurf->numVerts = surf->numVerts;
+
+			vaoSurf->minIndex = 0;
+			vaoSurf->maxIndex = surf->numVerts;
 
-			vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC);
+			vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC);
 
-			vboSurf->vbo->ofs_xyz       = ofs_xyz;
-			vboSurf->vbo->ofs_normal    = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].enabled = 1;
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->ofs_tangent   = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].enabled = 1;
 #endif
-			vboSurf->vbo->ofs_st        = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].enabled = 1;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].count = 2;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].type = GL_FLOAT;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].normalized = GL_FALSE;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].stride = sizeof(*verts);
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].stride = sizeof(*verts);
+
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].stride = sizeof(*normals);
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].stride = sizeof(*normals);
 
-			vboSurf->vbo->stride_xyz       = sizeof(*verts);
-			vboSurf->vbo->stride_normal    = sizeof(*normals);
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->stride_tangent   = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].stride = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].stride = sizeof(*tangents);
 #endif
-			vboSurf->vbo->stride_st        = sizeof(*st);
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].offset = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].stride = sizeof(*st);
 
-			vboSurf->vbo->size_xyz    = sizeof(*verts) * surf->numVerts;
-			vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts;
+			vaoSurf->vao->size_xyz    = sizeof(*verts)   * surf->numVerts;
+			vaoSurf->vao->size_normal = sizeof(*normals) * surf->numVerts;
 
-			ri.Free(data);
+			Vao_SetVertexPointers(vaoSurf->vao);
 
-			vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numIndexes, surf->indexes, VBO_USAGE_STATIC);
+			ri.Free(data);
 		}
 	}
 
@@ -1355,14 +1394,14 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, const char *modN
 
 
 	{
-		srfVBOMDVMesh_t *vboSurf;
+		srfVaoMdvMesh_t *vaoSurf;
 
-		mdvModel->numVBOSurfaces = mdvModel->numSurfaces;
-		mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low);
+		mdvModel->numVaoSurfaces = mdvModel->numSurfaces;
+		mdvModel->vaoSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vaoSurfaces) * mdvModel->numSurfaces, h_low);
 
-		vboSurf = mdvModel->vboSurfaces;
+		vaoSurf = mdvModel->vaoSurfaces;
 		surf = mdvModel->surfaces;
-		for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++)
+		for (i = 0; i < mdvModel->numSurfaces; i++, vaoSurf++, surf++)
 		{
 			vec3_t *verts;
 			vec2_t *texcoords;
@@ -1412,13 +1451,13 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, const char *modN
 
 				VectorCopy(v->xyz,       verts[j]);
 
-				normals[j] = R_VboPackNormal(v->normal);
+				normals[j] = R_VaoPackNormal(v->normal);
 #ifdef USE_VERT_TANGENT_SPACE
 				CrossProduct(v->normal, v->tangent, nxt);
 				VectorCopy(v->tangent, tangent);
 				tangent[3] = (DotProduct(nxt, v->bitangent) < 0.0f) ? -1.0f : 1.0f;
 
-				tangents[j] = R_VboPackTangent(tangent);
+				tangents[j] = R_VaoPackTangent(tangent);
 #endif
 			}
 
@@ -1428,37 +1467,76 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, const char *modN
 				texcoords[j][1] = st->st[1];
 			}
 
-			vboSurf->surfaceType = SF_VBO_MDVMESH;
-			vboSurf->mdvModel = mdvModel;
-			vboSurf->mdvSurface = surf;
-			vboSurf->numIndexes = surf->numIndexes;
-			vboSurf->numVerts = surf->numVerts;
-			
-			vboSurf->minIndex = 0;
-			vboSurf->maxIndex = surf->numVerts;
+			vaoSurf->surfaceType = SF_VAO_MDVMESH;
+			vaoSurf->mdvModel = mdvModel;
+			vaoSurf->mdvSurface = surf;
+			vaoSurf->numIndexes = surf->numIndexes;
+			vaoSurf->numVerts = surf->numVerts;
+
+			vaoSurf->minIndex = 0;
+			vaoSurf->maxIndex = surf->numVerts;
 
-			vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC);
+			vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC);
 
-			vboSurf->vbo->ofs_xyz       = ofs_xyz;
-			vboSurf->vbo->ofs_normal    = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].enabled = 1;
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->ofs_tangent   = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].enabled = 1;
 #endif
-			vboSurf->vbo->ofs_st        = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].enabled = 1;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].count = 2;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].type = GL_FLOAT;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].normalized = GL_FALSE;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].stride = sizeof(*verts);
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].stride = sizeof(*verts);
+
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].stride = sizeof(*normals);
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].stride = sizeof(*normals);
 
-			vboSurf->vbo->stride_xyz       = sizeof(*verts);
-			vboSurf->vbo->stride_normal    = sizeof(*normals);
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->stride_tangent   = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].stride = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].stride = sizeof(*tangents);
 #endif
-			vboSurf->vbo->stride_st        = sizeof(*st);
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].offset = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].stride = sizeof(*st);
 
-			vboSurf->vbo->size_xyz    = sizeof(*verts) * surf->numVerts;
-			vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts;
+			vaoSurf->vao->size_xyz    = sizeof(*verts)   * surf->numVerts;
+			vaoSurf->vao->size_normal = sizeof(*normals) * surf->numVerts;
 
-			ri.Free(data);
+			Vao_SetVertexPointers(vaoSurf->vao);
 
-			vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numIndexes, surf->indexes, VBO_USAGE_STATIC);
+			ri.Free(data);
 		}
 	}
 
diff --git a/MP/code/rend2/tr_model_iqm.c b/MP/code/rend2/tr_model_iqm.c
index c88dd88..79ac510 100644
--- a/MP/code/rend2/tr_model_iqm.c
+++ b/MP/code/rend2/tr_model_iqm.c
@@ -1130,7 +1130,7 @@ void RB_IQMSurfaceAnim( surfaceType_t *surface ) {
 			normal[1] = DotProduct(&nrmMat[3], &data->normals[3*vtx]);
 			normal[2] = DotProduct(&nrmMat[6], &data->normals[3*vtx]);
 
-			*outNormal = R_VboPackNormal(normal);
+			*outNormal = R_VaoPackNormal(normal);
 
 #ifdef USE_VERT_TANGENT_SPACE
 			tangent[0] = DotProduct(&nrmMat[0], &data->tangents[4*vtx]);
@@ -1138,7 +1138,7 @@ void RB_IQMSurfaceAnim( surfaceType_t *surface ) {
 			tangent[2] = DotProduct(&nrmMat[6], &data->tangents[4*vtx]);
 			tangent[3] = data->tangents[4*vtx+3];
 
-			*outTangent++ = R_VboPackTangent(tangent);
+			*outTangent++ = R_VaoPackTangent(tangent);
 #endif
 		}
 
diff --git a/MP/code/rend2/tr_shade.c b/MP/code/rend2/tr_shade.c
index f5a6718..35b6db0 100644
--- a/MP/code/rend2/tr_shade.c
+++ b/MP/code/rend2/tr_shade.c
@@ -47,7 +47,7 @@ R_DrawElements
 ==================
 */
 
-void R_DrawElementsVBO( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex )
+void R_DrawElementsVao( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex )
 {
 	if (glRefConfig.drawRangeElements)
 		qglDrawRangeElementsEXT(GL_TRIANGLES, minIndex, maxIndex, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
@@ -57,7 +57,7 @@ void R_DrawElementsVBO( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex
 }
 
 
-static void R_DrawMultiElementsVBO( int multiDrawPrimitives, glIndex_t *multiDrawMinIndex, glIndex_t *multiDrawMaxIndex, 
+static void R_DrawMultiElementsVao( int multiDrawPrimitives, glIndex_t *multiDrawMinIndex, glIndex_t *multiDrawMaxIndex,
 	GLsizei *multiDrawNumIndexes, glIndex_t **multiDrawFirstIndex)
 {
 	if (glRefConfig.multiDrawArrays)
@@ -159,7 +159,6 @@ static void DrawTris (shaderCommands_t *input) {
 		shaderProgram_t *sp = &tr.textureColorShader;
 		vec4_t color;
 
-		GLSL_VertexAttribsState(ATTR_POSITION);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -168,11 +167,11 @@ static void DrawTris (shaderCommands_t *input) {
 
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 	}
 
@@ -216,7 +215,7 @@ void RB_BeginSurface( shader_t *shader, int fogNum, int cubemapIndex ) {
 	tess.xstages = state->stages;
 	tess.numPasses = state->numUnfoggedPasses;
 	tess.currentStageIteratorFunc = state->optimalStageIteratorFunc;
-	tess.useInternalVBO = qtrue;
+	tess.useInternalVao = qtrue;
 
 	tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
 	if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) {
@@ -447,11 +446,11 @@ static void ProjectDlightTexture( void ) {
 		if (tess.multiDrawPrimitives)
 		{
 			shaderCommands_t *input = &tess;
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+			R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 		}
 
 		backEnd.pc.c_totalIndexes += tess.numIndexes;
@@ -908,11 +907,11 @@ static void ForwardDlight( void ) {
 
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 
 		backEnd.pc.c_totalIndexes += tess.numIndexes;
@@ -984,11 +983,11 @@ static void ProjectPshadowVBOGLSL( void ) {
 
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 
 		backEnd.pc.c_totalIndexes += tess.numIndexes;
@@ -1121,11 +1120,11 @@ static void RB_FogPass( int wolfFog ) {
 	if (tess.multiDrawPrimitives)
 	{
 		shaderCommands_t *input = &tess;
-		R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+		R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 	}
 	else
 	{
-		R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+		R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 	}
 }
 
@@ -1526,11 +1525,11 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input )
 		//
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 
 		// allow skipping out to show just lightmaps during development
@@ -1590,11 +1589,11 @@ static void RB_RenderShadowmap( shaderCommands_t *input )
 
 			if (input->multiDrawPrimitives)
 			{
-				R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+				R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 			}
 			else
 			{
-				R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+				R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 			}
 		}
 	}
@@ -1617,20 +1616,20 @@ void RB_StageIteratorGeneric( void )
 		return;
 	}
 
-	if (tess.useInternalVBO)
+	if (tess.useInternalVao)
 	{
 		RB_DeformTessGeometry();
 	}
 
 	vertexAttribs = RB_CalcShaderVertexAttribs( input );
 
-	if (tess.useInternalVBO)
+	if (tess.useInternalVao)
 	{
-		RB_UpdateVBOs(vertexAttribs);
+		RB_UpdateTessVao(vertexAttribs);
 	}
 	else
 	{
-		backEnd.pc.c_staticVboDraws++;
+		backEnd.pc.c_staticVaoDraws++;
 	}
 
 	//
@@ -1665,13 +1664,13 @@ void RB_StageIteratorGeneric( void )
 	if ( input->shader->polygonOffset )
 	{
 		qglEnable( GL_POLYGON_OFFSET_FILL );
-		qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
 	}
 
 	//
 	// Set vertex attribs and pointers
 	//
-	GLSL_VertexAttribsState(vertexAttribs);
+	if (glState.vertexAnimation)
+		GLSL_VertexAttribPointers(vertexAttribs & (ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TANGENT | ATTR_TANGENT2));
 
 	//
 	// render depth if in depthfill mode
diff --git a/MP/code/rend2/tr_shade_calc.c b/MP/code/rend2/tr_shade_calc.c
index b499134..cb96aa0 100644
--- a/MP/code/rend2/tr_shade_calc.c
+++ b/MP/code/rend2/tr_shade_calc.c
@@ -153,7 +153,7 @@ void RB_CalcDeformVertexes( deformStage_t *ds ) {
 			float dot;
 			vec3_t fNormal;
 
-			R_VboUnpackNormal(fNormal, *normal);
+			R_VaoUnpackNormal(fNormal, *normal);
 
 			scale = WAVEVALUE( table, ds->deformationWave.base,
 							   ds->deformationWave.amplitude,
@@ -181,7 +181,7 @@ void RB_CalcDeformVertexes( deformStage_t *ds ) {
 
 		for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal++ )
 		{
-			R_VboUnpackNormal(offset, *normal);
+			R_VaoUnpackNormal(offset, *normal);
 
 			xyz[0] += offset[0] * scale;
 			xyz[1] += offset[1] * scale;
@@ -200,7 +200,7 @@ void RB_CalcDeformVertexes( deformStage_t *ds ) {
 							   ds->deformationWave.phase + off,
 							   ds->deformationWave.frequency );
 
-			R_VboUnpackNormal(offset, *normal);
+			R_VaoUnpackNormal(offset, *normal);
 
 			xyz[0] += offset[0] * scale;
 			xyz[1] += offset[1] * scale;
@@ -225,7 +225,7 @@ void RB_CalcDeformNormals( deformStage_t *ds ) {
 	for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal++ ) {
 		vec3_t fNormal;
 
-		R_VboUnpackNormal(fNormal, *normal);
+		R_VaoUnpackNormal(fNormal, *normal);
 
 		scale = 0.98f;
 		scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
@@ -244,7 +244,7 @@ void RB_CalcDeformNormals( deformStage_t *ds ) {
 
 		VectorNormalizeFast( fNormal );
 
-		*normal = R_VboPackNormal(fNormal);
+		*normal = R_VaoPackNormal(fNormal);
 	}
 }
 
@@ -268,7 +268,7 @@ void RB_CalcBulgeVertexes( deformStage_t *ds ) {
 		float scale;
 		vec3_t fNormal;
 
-		R_VboUnpackNormal(fNormal, *normal);
+		R_VaoUnpackNormal(fNormal, *normal);
 
 		off = (float)( FUNCTABLE_SIZE / ( M_PI * 2 ) ) * ( st[0] * ds->bulgeWidth + now );
 
@@ -332,7 +332,7 @@ void DeformText( const char *text ) {
 	height[1] = 0;
 	height[2] = -1;
 
-	R_VboUnpackNormal(fNormal, tess.normal[0]);
+	R_VaoUnpackNormal(fNormal, tess.normal[0]);
 	CrossProduct( fNormal, height, width );
 
 	// find the midpoint of the box
@@ -821,7 +821,7 @@ void RB_CalcFireRiseEnvTexCoords( float *st ) {
 	{
 		VectorNormalizeFast( viewer );
 
-		R_VboUnpackNormal(fNormal, *normal);
+		R_VaoUnpackNormal(fNormal, *normal);
 
 		d = DotProduct( fNormal, viewer );
 
diff --git a/MP/code/rend2/tr_sky.c b/MP/code/rend2/tr_sky.c
index 2918c56..37bc21d 100644
--- a/MP/code/rend2/tr_sky.c
+++ b/MP/code/rend2/tr_sky.c
@@ -435,12 +435,11 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
 	tess.maxIndex = tess.numVertexes;
 
 	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
-	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
 /*
 	{
 		shaderProgram_t *sp = &tr.textureColorShader;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -456,7 +455,6 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
 		shaderProgram_t *sp = &tr.lightallShader[0];
 		vec4_t vector;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -480,7 +478,7 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
 		GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector);
 	}
 
-	R_DrawElementsVBO(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	//qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t)));
 	
@@ -554,12 +552,11 @@ static void DrawSkySideInner( struct image_s *image, const int mins[2], const in
 	tess.maxIndex = tess.numVertexes;
 
 	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
-	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
 /*
 	{
 		shaderProgram_t *sp = &tr.textureColorShader;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -575,7 +572,6 @@ static void DrawSkySideInner( struct image_s *image, const int mins[2], const in
 		shaderProgram_t *sp = &tr.lightallShader[0];
 		vec4_t vector;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -599,7 +595,7 @@ static void DrawSkySideInner( struct image_s *image, const int mins[2], const in
 		GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector);
 	}
 
-	R_DrawElementsVBO(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	//qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t)));
 	
diff --git a/MP/code/rend2/tr_surface.c b/MP/code/rend2/tr_surface.c
index 732e579..602f975 100644
--- a/MP/code/rend2/tr_surface.c
+++ b/MP/code/rend2/tr_surface.c
@@ -73,19 +73,18 @@ void RB_CheckOverflow( int verts, int indexes ) {
 	RB_BeginSurface( tess.shader, tess.fogNum, tess.cubemapIndex );
 }
 
-void RB_CheckVBOandIBO(VBO_t *vbo, IBO_t *ibo)
+void RB_CheckVao(vao_t *vao)
 {
-	if (!(vbo == glState.currentVBO && ibo == glState.currentIBO) || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES)
+	if (vao != glState.currentVao || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES)
 	{
 		RB_EndSurface();
 		RB_BeginSurface( tess.shader, tess.fogNum, tess.cubemapIndex );
 
-		R_BindVBO(vbo);
-		R_BindIBO(ibo);
+		R_BindVao(vao);
 	}
 
-	if (vbo != tess.vbo && ibo != tess.ibo)
-		tess.useInternalVBO = qfalse;
+	if (vao != tess.vao)
+		tess.useInternalVao = qfalse;
 }
 
 /*
@@ -133,7 +132,7 @@ void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4],
 	tess.normal[ndx] =
 	tess.normal[ndx+1] =
 	tess.normal[ndx+2] =
-	tess.normal[ndx+3] = R_VboPackNormal(normal);
+	tess.normal[ndx+3] = R_VaoPackNormal(normal);
 
 	// standard square texture coordinates
 	VectorSet2(tess.texCoords[ndx  ][0], s1, t1);
@@ -208,11 +207,9 @@ void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4])
 	tess.minIndex = 0;
 	tess.maxIndex = 3;
 
-	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
 
-	GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
-
-	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	tess.numIndexes = 0;
 	tess.numVertexes = 0;
@@ -353,7 +350,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 	glIndex_t      *outIndex;
 	float		*color;
 
-	RB_CheckVBOandIBO(tess.vbo, tess.ibo);
+	RB_CheckVao(tess.vao);
 
 	RB_CHECKOVERFLOW( numVerts, numIndexes );
 
@@ -377,7 +374,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 		dv = verts;
 		normal = &tess.normal[ tess.numVertexes ];
 		for ( i = 0 ; i < numVerts ; i++, dv++, normal++ )
-			*normal = R_VboPackNormal(dv->normal);
+			*normal = R_VaoPackNormal(dv->normal);
 	}
 
 #ifdef USE_VERT_TANGENT_SPACE
@@ -386,7 +383,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 		dv = verts;
 		tangent = &tess.tangent[ tess.numVertexes ];
 		for ( i = 0 ; i < numVerts ; i++, dv++, tangent++ )
-			*tangent = R_VboPackTangent(dv->tangent);
+			*tangent = R_VaoPackTangent(dv->tangent);
 	}
 #endif
 
@@ -419,7 +416,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 		dv = verts;
 		lightdir = &tess.lightdir[ tess.numVertexes ];
 		for ( i = 0 ; i < numVerts ; i++, dv++, lightdir++ )
-			*lightdir = R_VboPackNormal(dv->lightdir);
+			*lightdir = R_VaoPackNormal(dv->lightdir);
 	}
 
 #if 0  // nothing even uses vertex dlightbits
@@ -434,12 +431,12 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 	tess.numVertexes += numVerts;
 }
 
-static qboolean RB_SurfaceVbo(VBO_t *vbo, IBO_t *ibo, int numVerts, int numIndexes, int firstIndex, int minIndex, int maxIndex, int dlightBits, int pshadowBits, qboolean shaderCheck)
+static qboolean RB_SurfaceVao(vao_t *vao, int numVerts, int numIndexes, int firstIndex, int minIndex, int maxIndex, int dlightBits, int pshadowBits, qboolean shaderCheck)
 {
 	int i, mergeForward, mergeBack;
 	GLvoid *firstIndexOffset, *lastIndexOffset;
 
-	if (!vbo || !ibo)
+	if (!vao)
 	{
 		return qfalse;
 	}
@@ -449,7 +446,7 @@ static qboolean RB_SurfaceVbo(VBO_t *vbo, IBO_t *ibo, int numVerts, int numIndex
 		return qfalse;
 	}
 
-	RB_CheckVBOandIBO(vbo, ibo);
+	RB_CheckVao(vao);
 
 	tess.dlightBits |= dlightBits;
 	tess.pshadowBits |= pshadowBits;
@@ -543,7 +540,7 @@ RB_SurfaceTriangles
 =============
 */
 static void RB_SurfaceTriangles( srfBspSurface_t *srf ) {
-	if( RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes,
+	if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes,
 				srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) )
 	{
 		return;
@@ -629,16 +626,15 @@ static void RB_SurfaceBeam( void ) {
 	tess.maxIndex = tess.numVertexes;
 
 	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
-	RB_UpdateVBOs(ATTR_POSITION);
+	RB_UpdateTessVao(ATTR_POSITION);
 	
-	GLSL_VertexAttribsState(ATTR_POSITION);
 	GLSL_BindProgram(sp);
 		
 	GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
 
 	GLSL_SetUniformVec4(sp, UNIFORM_COLOR, colorRed);
 
-	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	tess.numIndexes = 0;
 	tess.numVertexes = 0;
@@ -1175,7 +1171,7 @@ static void LerpMeshVertexes_scalar(mdvSurface_t *surf, float backlerp)
 			VectorCopy(newVerts->xyz,    outXyz);
 			VectorCopy(newVerts->normal, normal);
 
-			*outNormal = R_VboPackNormal(normal);
+			*outNormal = R_VaoPackNormal(normal);
 
 			newVerts++;
 			outXyz += 4;
@@ -1200,7 +1196,7 @@ static void LerpMeshVertexes_scalar(mdvSurface_t *surf, float backlerp)
 			VectorLerp(newVerts->normal, oldVerts->normal, backlerp, normal);
 			VectorNormalize(normal);
 
-			*outNormal = R_VboPackNormal(normal);
+			*outNormal = R_VaoPackNormal(normal);
 
 			newVerts++;
 			oldVerts++;
@@ -1367,7 +1363,7 @@ static void LerpCMeshVertexes( mdcSurface_t *surf, float backlerp ) {
 				fNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK];
 			}
 
-			*outNormal = R_VboPackNormal(fNormal);
+			*outNormal = R_VaoPackNormal(fNormal);
 		}
 	} else {
 		//
@@ -1437,7 +1433,7 @@ static void LerpCMeshVertexes( mdcSurface_t *surf, float backlerp ) {
 
 			VectorNormalize( fNormal );
 			
-			*outNormal = R_VboPackNormal(fNormal);
+			*outNormal = R_VaoPackNormal(fNormal);
 		}
 	}
 }
@@ -1502,7 +1498,7 @@ RB_SurfaceFace
 ==============
 */
 static void RB_SurfaceFace( srfBspSurface_t *srf ) {
-	if( RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes,
+	if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes,
 					srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) )
 	{
 		return;
@@ -1572,7 +1568,7 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) {
 	int     pshadowBits;
 	//int		*vDlightBits;
 
-	if( RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes,
+	if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes,
 					srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) )
 	{
 		return;
@@ -1665,13 +1661,13 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) {
 
 				if ( tess.shader->vertexAttribs & ATTR_NORMAL )
 				{
-					*normal++ = R_VboPackNormal(dv->normal);
+					*normal++ = R_VaoPackNormal(dv->normal);
 				}
 
 #ifdef USE_VERT_TANGENT_SPACE
 				if ( tess.shader->vertexAttribs & ATTR_TANGENT )
 				{
-					*tangent++ = R_VboPackTangent(dv->tangent);
+					*tangent++ = R_VaoPackTangent(dv->tangent);
 				}
 #endif
 
@@ -1695,7 +1691,7 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) {
 
 				if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION )
 				{
-					*lightdir++ = R_VboPackNormal(dv->lightdir);
+					*lightdir++ = R_VaoPackNormal(dv->lightdir);
 				}
 
 				//*vDlightBits++ = dlightBits;
@@ -1821,31 +1817,30 @@ static void RB_SurfaceFlare( srfFlare_t *surf ) {
 		RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, 1.0f, surf->normal, 0, qtrue);
 }
 
-static void RB_SurfaceVBOMesh(srfBspSurface_t * srf)
+static void RB_SurfaceVaoMesh(srfBspSurface_t * srf)
 {
-	RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes, srf->firstIndex,
+	RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes, srf->firstIndex,
 			srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qfalse );
 }
 
-void RB_SurfaceVBOMDVMesh(srfVBOMDVMesh_t * surface)
+void RB_SurfaceVaoMdvMesh(srfVaoMdvMesh_t * surface)
 {
 	//mdvModel_t     *mdvModel;
 	//mdvSurface_t   *mdvSurface;
 	refEntity_t    *refEnt;
 
-	GLimp_LogComment("--- RB_SurfaceVBOMDVMesh ---\n");
+	GLimp_LogComment("--- RB_SurfaceVaoMdvMesh ---\n");
 
-	if(!surface->vbo || !surface->ibo)
+	if(!surface->vao)
 		return;
 
-	//RB_CheckVBOandIBO(surface->vbo, surface->ibo);
+	//RB_CheckVao(surface->vao);
 	RB_EndSurface();
 	RB_BeginSurface( tess.shader, tess.fogNum, tess.cubemapIndex );
 
-	R_BindVBO(surface->vbo);
-	R_BindIBO(surface->ibo);
+	R_BindVao(surface->vao);
 
-	tess.useInternalVBO = qfalse;
+	tess.useInternalVao = qfalse;
 
 	tess.numIndexes += surface->numIndexes;
 	tess.numVertexes += surface->numVerts;
@@ -1868,7 +1863,8 @@ void RB_SurfaceVBOMDVMesh(srfVBOMDVMesh_t * surface)
 
 	glState.vertexAttribsOldFrame = refEnt->oldframe;
 	glState.vertexAttribsNewFrame = refEnt->frame;
-	glState.vertexAnimation = qtrue;
+	if (surface->mdvModel->numFrames > 1)
+		glState.vertexAnimation = qtrue;
 
 	RB_EndSurface();
 
@@ -1901,6 +1897,6 @@ void( *rb_surfaceTable[SF_NUM_SURFACE_TYPES] ) ( void * ) = {
 	( void( * ) ( void* ) )RB_SurfaceFlare,		// SF_FLARE,
 	( void( * ) ( void* ) )RB_SurfaceEntity,	// SF_ENTITY
 	( void( * ) ( void* ) )RB_SurfaceDisplayList,	// SF_DISPLAY_LIST
-	( void( * ) ( void* ) )RB_SurfaceVBOMesh,	// SF_VBO_MESH,
-	( void( * ) ( void* ) )RB_SurfaceVBOMDVMesh,	// SF_VBO_MDVMESH
+	( void( * ) ( void* ) )RB_SurfaceVaoMesh,	// SF_VAO_MESH,
+	( void( * ) ( void* ) )RB_SurfaceVaoMdvMesh,	// SF_VAO_MDVMESH
 };
diff --git a/MP/code/rend2/tr_vbo.c b/MP/code/rend2/tr_vbo.c
index dc0f33b..5c44b0e 100644
--- a/MP/code/rend2/tr_vbo.c
+++ b/MP/code/rend2/tr_vbo.c
@@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 #include "tr_local.h"
 
 
-uint32_t R_VboPackTangent(vec4_t v)
+uint32_t R_VaoPackTangent(vec4_t v)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -41,7 +41,7 @@ uint32_t R_VboPackTangent(vec4_t v)
 	}
 }
 
-uint32_t R_VboPackNormal(vec3_t v)
+uint32_t R_VaoPackNormal(vec3_t v)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -57,7 +57,7 @@ uint32_t R_VboPackNormal(vec3_t v)
 	}
 }
 
-void R_VboUnpackTangent(vec4_t v, uint32_t b)
+void R_VaoUnpackTangent(vec4_t v, uint32_t b)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -75,7 +75,7 @@ void R_VboUnpackTangent(vec4_t v, uint32_t b)
 	}
 }
 
-void R_VboUnpackNormal(vec3_t v, uint32_t b)
+void R_VaoUnpackNormal(vec3_t v, uint32_t b)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -91,790 +91,530 @@ void R_VboUnpackNormal(vec3_t v, uint32_t b)
 	}
 }
 
+
+void Vao_SetVertexPointers(vao_t *vao)
+{
+	int i;
+
+	// set vertex pointers
+	for (i = 0; i < ATTR_INDEX_COUNT; i++)
+	{
+		if (vao->attribs[i].enabled)
+		{
+			qglVertexAttribPointerARB((GLuint)i,
+								  (GLint)vao->attribs[i].count,
+								  (GLenum)vao->attribs[i].type, 
+								  (GLboolean)vao->attribs[i].normalized, 
+								  (GLsizei)vao->attribs[i].stride,
+								  BUFFER_OFFSET(vao->attribs[i].offset));
+			qglEnableVertexAttribArrayARB(i);
+		}
+		else
+		{
+			qglDisableVertexAttribArrayARB(i);
+		}
+	}
+}
+
 /*
 ============
-R_CreateVBO
+R_CreateVao
 ============
 */
-VBO_t          *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage)
+vao_t *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage)
 {
-	VBO_t          *vbo;
+	vao_t          *vao;
 	int				glUsage;
 
 	switch (usage)
 	{
-		case VBO_USAGE_STATIC:
+		case VAO_USAGE_STATIC:
 			glUsage = GL_STATIC_DRAW_ARB;
 			break;
 
-		case VBO_USAGE_DYNAMIC:
+		case VAO_USAGE_DYNAMIC:
 			glUsage = GL_DYNAMIC_DRAW_ARB;
 			break;
 
 		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
+			Com_Error(ERR_FATAL, "bad vaoUsage_t given: %i", usage);
 			return NULL;
 	}
 
 	if(strlen(name) >= MAX_QPATH)
 	{
-		ri.Error(ERR_DROP, "R_CreateVBO: \"%s\" is too long", name);
+		ri.Error(ERR_DROP, "R_CreateVao: \"%s\" is too long", name);
 	}
 
-	if ( tr.numVBOs == MAX_VBOS ) {
-		ri.Error( ERR_DROP, "R_CreateVBO: MAX_VBOS hit");
+	if ( tr.numVaos == MAX_VAOS ) {
+		ri.Error( ERR_DROP, "R_CreateVao: MAX_VAOS hit");
 	}
 
 	R_IssuePendingRenderCommands();
 
-	vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low);
-	tr.numVBOs++;
+	vao = tr.vaos[tr.numVaos] = ri.Hunk_Alloc(sizeof(*vao), h_low);
+	tr.numVaos++;
+
+	memset(vao, 0, sizeof(*vao));
+
+	Q_strncpyz(vao->name, name, sizeof(vao->name));
+
 
-	memset(vbo, 0, sizeof(*vbo));
+	if (glRefConfig.vertexArrayObject)
+	{
+		qglGenVertexArraysARB(1, &vao->vao);
+		qglBindVertexArrayARB(vao->vao);
+	}
 
-	Q_strncpyz(vbo->name, name, sizeof(vbo->name));
 
-	vbo->vertexesSize = vertexesSize;
+	vao->vertexesSize = vertexesSize;
 
-	qglGenBuffersARB(1, &vbo->vertexesVBO);
+	qglGenBuffersARB(1, &vao->vertexesVBO);
 
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
 	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexesSize, vertexes, glUsage);
 
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
 
-	glState.currentVBO = NULL;
+	vao->indexesSize = indexesSize;
+
+	qglGenBuffersARB(1, &vao->indexesIBO);
+
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesIBO);
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
+
+
+	glState.currentVao = vao;
 
 	GL_CheckErrors();
 
-	return vbo;
+	return vao;
 }
 
 /*
 ============
-R_CreateVBO2
+R_CreateVao2
 ============
 */
-VBO_t          *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * verts, unsigned int stateBits, vboUsage_t usage)
+vao_t *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *indexes)
 {
-	VBO_t          *vbo;
+	vao_t          *vao;
 	int             i;
 
 	byte           *data;
 	int             dataSize;
 	int             dataOfs;
 
-	int				glUsage;
-
-	switch (usage)
-	{
-		case VBO_USAGE_STATIC:
-			glUsage = GL_STATIC_DRAW_ARB;
-			break;
+	int				glUsage = GL_STATIC_DRAW_ARB;
 
-		case VBO_USAGE_DYNAMIC:
-			glUsage = GL_DYNAMIC_DRAW_ARB;
-			break;
-
-		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
-			return NULL;
-	}
-
-	if(!numVertexes)
+	if(!numVertexes || !numIndexes)
 		return NULL;
 
 	if(strlen(name) >= MAX_QPATH)
 	{
-		ri.Error(ERR_DROP, "R_CreateVBO2: \"%s\" is too long", name);
+		ri.Error(ERR_DROP, "R_CreateVao2: \"%s\" is too long", name);
 	}
 
-	if ( tr.numVBOs == MAX_VBOS ) {
-		ri.Error( ERR_DROP, "R_CreateVBO2: MAX_VBOS hit");
+	if ( tr.numVaos == MAX_VAOS ) {
+		ri.Error( ERR_DROP, "R_CreateVao2: MAX_VAOS hit");
 	}
 
 	R_IssuePendingRenderCommands();
 
-	vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low);
-	tr.numVBOs++;
-
-	memset(vbo, 0, sizeof(*vbo));
-
-	Q_strncpyz(vbo->name, name, sizeof(vbo->name));
-
-	if (usage == VBO_USAGE_STATIC)
-	{
-		// since these vertex attributes are never altered, interleave them
-		vbo->ofs_xyz = 0;
-		dataSize = sizeof(verts[0].xyz);
-
-		if(stateBits & ATTR_NORMAL)
-		{
-			vbo->ofs_normal = dataSize;
-			dataSize += sizeof(uint32_t);
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(stateBits & ATTR_TANGENT)
-		{
-			vbo->ofs_tangent = dataSize;
-			dataSize += sizeof(uint32_t);
-		}
-#endif
-
-		if(stateBits & ATTR_TEXCOORD)
-		{
-			vbo->ofs_st = dataSize;
-			dataSize += sizeof(verts[0].st);
-		}
-
-		if(stateBits & ATTR_LIGHTCOORD)
-		{
-			vbo->ofs_lightmap = dataSize;
-			dataSize += sizeof(verts[0].lightmap);
-		}
+	vao = tr.vaos[tr.numVaos] = ri.Hunk_Alloc(sizeof(*vao), h_low);
+	tr.numVaos++;
 
-		if(stateBits & ATTR_COLOR)
-		{
-			vbo->ofs_vertexcolor = dataSize;
-			dataSize += sizeof(verts[0].vertexColors);
-		}
+	memset(vao, 0, sizeof(*vao));
 
-		if(stateBits & ATTR_LIGHTDIRECTION)
-		{
-			vbo->ofs_lightdir = dataSize;
-			dataSize += sizeof(uint32_t);
-		}
+	Q_strncpyz(vao->name, name, sizeof(vao->name));
 
-		vbo->stride_xyz         = dataSize;
-		vbo->stride_normal      = dataSize;
+	// since these vertex attributes are never altered, interleave them
+	vao->attribs[ATTR_INDEX_POSITION      ].enabled = 1;
+	vao->attribs[ATTR_INDEX_NORMAL        ].enabled = 1;
 #ifdef USE_VERT_TANGENT_SPACE
-		vbo->stride_tangent     = dataSize;
+	vao->attribs[ATTR_INDEX_TANGENT       ].enabled = 1;
 #endif
-		vbo->stride_st          = dataSize;
-		vbo->stride_lightmap    = dataSize;
-		vbo->stride_vertexcolor = dataSize;
-		vbo->stride_lightdir    = dataSize;
-
-		// create VBO
-		dataSize *= numVertexes;
-		data = ri.Hunk_AllocateTempMemory(dataSize);
-		dataOfs = 0;
-
-		//ri.Printf(PRINT_ALL, "CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor,
-			//vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor);
-
-		for (i = 0; i < numVertexes; i++)
-		{
-			// xyz
-			memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
-			dataOfs += sizeof(verts[i].xyz);
-
-			// normal
-			if(stateBits & ATTR_NORMAL)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackNormal(verts[i].normal);
-
-				dataOfs += sizeof(uint32_t);
-			}
-
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].enabled = 1;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].enabled = 1;
+	vao->attribs[ATTR_INDEX_COLOR         ].enabled = 1;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].count = 3;
+	vao->attribs[ATTR_INDEX_NORMAL        ].count = 4;
+	vao->attribs[ATTR_INDEX_TANGENT       ].count = 4;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].count = 2;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].count = 2;
+	vao->attribs[ATTR_INDEX_COLOR         ].count = 4;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_NORMAL        ].type = glRefConfig.packedNormalDataType;
+	vao->attribs[ATTR_INDEX_TANGENT       ].type = glRefConfig.packedNormalDataType;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_COLOR         ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = glRefConfig.packedNormalDataType;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_NORMAL        ].normalized = GL_TRUE;
+	vao->attribs[ATTR_INDEX_TANGENT       ].normalized = GL_TRUE;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_COLOR         ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].offset = 0;        dataSize  = sizeof(verts[0].xyz);
+	vao->attribs[ATTR_INDEX_NORMAL        ].offset = dataSize; dataSize += sizeof(uint32_t);
 #ifdef USE_VERT_TANGENT_SPACE
-			// tangent
-			if(stateBits & ATTR_TANGENT)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackTangent(verts[i].tangent);
-
-				dataOfs += sizeof(uint32_t);
-			}
+	vao->attribs[ATTR_INDEX_TANGENT       ].offset = dataSize; dataSize += sizeof(uint32_t);
 #endif
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].offset = dataSize; dataSize += sizeof(verts[0].st);
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].offset = dataSize; dataSize += sizeof(verts[0].lightmap);
+	vao->attribs[ATTR_INDEX_COLOR         ].offset = dataSize; dataSize += sizeof(verts[0].vertexColors);
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(uint32_t);
 
-			// vertex texcoords
-			if(stateBits & ATTR_TEXCOORD)
-			{
-				memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
-				dataOfs += sizeof(verts[i].st);
-			}
-
-			// feed vertex lightmap texcoords
-			if(stateBits & ATTR_LIGHTCOORD)
-			{
-				memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
-				dataOfs += sizeof(verts[i].lightmap);
-			}
-
-			// feed vertex colors
-			if(stateBits & ATTR_COLOR)
-			{
-				memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
-				dataOfs += sizeof(verts[i].vertexColors);
-			}
-
-			// feed vertex light directions
-			if(stateBits & ATTR_LIGHTDIRECTION)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
+	vao->attribs[ATTR_INDEX_POSITION      ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_NORMAL        ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_TANGENT       ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_COLOR         ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize;
 
-				*p = R_VboPackNormal(verts[i].lightdir);
 
-				dataOfs += sizeof(uint32_t);
-			}
-		}
-	}
-	else
+	if (glRefConfig.vertexArrayObject)
 	{
-		// since these vertex attributes may be changed, put them in flat arrays
-		dataSize = sizeof(verts[0].xyz);
-
-		if(stateBits & ATTR_NORMAL)
-		{
-			dataSize += sizeof(uint32_t);
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(stateBits & ATTR_TANGENT)
-		{
-			dataSize += sizeof(uint32_t);
-		}
-#endif
-
-		if(stateBits & ATTR_TEXCOORD)
-		{
-			dataSize += sizeof(verts[0].st);
-		}
-
-		if(stateBits & ATTR_LIGHTCOORD)
-		{
-			dataSize += sizeof(verts[0].lightmap);
-		}
-
-		if(stateBits & ATTR_COLOR)
-		{
-			dataSize += sizeof(verts[0].vertexColors);
-		}
-
-		if(stateBits & ATTR_LIGHTDIRECTION)
-		{
-			dataSize += sizeof(uint32_t);
-		}
+		qglGenVertexArraysARB(1, &vao->vao);
+		qglBindVertexArrayARB(vao->vao);
+	}
 
-		// create VBO
-		dataSize *= numVertexes;
-		data = ri.Hunk_AllocateTempMemory(dataSize);
-		dataOfs = 0;
 
-		vbo->ofs_xyz            = 0;
-		vbo->ofs_normal         = 0;
-#ifdef USE_VERT_TANGENT_SPACE
-		vbo->ofs_tangent        = 0;
-#endif
-		vbo->ofs_st             = 0;
-		vbo->ofs_lightmap       = 0;
-		vbo->ofs_vertexcolor    = 0;
-		vbo->ofs_lightdir       = 0;
+	// create VBO
+	dataSize *= numVertexes;
+	data = ri.Hunk_AllocateTempMemory(dataSize);
+	dataOfs = 0;
 
-		vbo->stride_xyz         = sizeof(verts[0].xyz);
-		vbo->stride_normal      = sizeof(uint32_t);
-#ifdef USE_VERT_TANGENT_SPACE
-		vbo->stride_tangent     = sizeof(uint32_t);
-#endif
-		vbo->stride_vertexcolor = sizeof(verts[0].vertexColors);
-		vbo->stride_st          = sizeof(verts[0].st);
-		vbo->stride_lightmap    = sizeof(verts[0].lightmap);
-		vbo->stride_lightdir    = sizeof(uint32_t);
-
-		//ri.Printf(PRINT_ALL, "2CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor,
-			//vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor);
+	for (i = 0; i < numVertexes; i++)
+	{
+		uint32_t *p;
 
 		// xyz
-		for (i = 0; i < numVertexes; i++)
-		{
-			memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
-			dataOfs += sizeof(verts[i].xyz);
-		}
+		memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
+		dataOfs += sizeof(verts[i].xyz);
 
 		// normal
-		if(stateBits & ATTR_NORMAL)
-		{
-			vbo->ofs_normal = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackNormal(verts[i].normal);
-
-				dataOfs += sizeof(uint32_t);
-			}
-		}
+		p = (uint32_t *)(data + dataOfs);
+		*p = R_VaoPackNormal(verts[i].normal);
+		dataOfs += sizeof(uint32_t);
 
 #ifdef USE_VERT_TANGENT_SPACE
 		// tangent
-		if(stateBits & ATTR_TANGENT)
-		{
-			vbo->ofs_tangent = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackTangent(verts[i].tangent);
-
-				dataOfs += sizeof(uint32_t);
-			}
-		}
+		p = (uint32_t *)(data + dataOfs);
+		*p = R_VaoPackTangent(verts[i].tangent);
+		dataOfs += sizeof(uint32_t);
 #endif
 
 		// vertex texcoords
-		if(stateBits & ATTR_TEXCOORD)
-		{
-			vbo->ofs_st = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
-				dataOfs += sizeof(verts[i].st);
-			}
-		}
+		memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
+		dataOfs += sizeof(verts[i].st);
 
 		// feed vertex lightmap texcoords
-		if(stateBits & ATTR_LIGHTCOORD)
-		{
-			vbo->ofs_lightmap = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
-				dataOfs += sizeof(verts[i].lightmap);
-			}
-		}
+		memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
+		dataOfs += sizeof(verts[i].lightmap);
 
 		// feed vertex colors
-		if(stateBits & ATTR_COLOR)
-		{
-			vbo->ofs_vertexcolor = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
-				dataOfs += sizeof(verts[i].vertexColors);
-			}
-		}
-
-		// feed vertex lightdirs
-		if(stateBits & ATTR_LIGHTDIRECTION)
-		{
-			vbo->ofs_lightdir = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackNormal(verts[i].lightdir);
-
-				dataOfs += sizeof(uint32_t);
-			}
-		}
-	}
-
-
-	vbo->vertexesSize = dataSize;
-
-	qglGenBuffersARB(1, &vbo->vertexesVBO);
-
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
-	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, data, glUsage);
-
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
-
-	glState.currentVBO = NULL;
-
-	GL_CheckErrors();
-
-	ri.Hunk_FreeTempMemory(data);
+		memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
+		dataOfs += sizeof(verts[i].vertexColors);
 
-	return vbo;
-}
-
-
-/*
-============
-R_CreateIBO
-============
-*/
-IBO_t          *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage)
-{
-	IBO_t          *ibo;
-	int				glUsage;
-
-	switch (usage)
-	{
-		case VBO_USAGE_STATIC:
-			glUsage = GL_STATIC_DRAW_ARB;
-			break;
-
-		case VBO_USAGE_DYNAMIC:
-			glUsage = GL_DYNAMIC_DRAW_ARB;
-			break;
-
-		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
-			return NULL;
-	}
-
-	if(strlen(name) >= MAX_QPATH)
-	{
-		ri.Error(ERR_DROP, "R_CreateIBO: \"%s\" is too long", name);
-	}
-
-	if ( tr.numIBOs == MAX_IBOS ) {
-		ri.Error( ERR_DROP, "R_CreateIBO: MAX_IBOS hit");
-	}
-
-	R_IssuePendingRenderCommands();
-
-	ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
-	tr.numIBOs++;
-
-	Q_strncpyz(ibo->name, name, sizeof(ibo->name));
-
-	ibo->indexesSize = indexesSize;
-
-	qglGenBuffersARB(1, &ibo->indexesVBO);
-
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
-	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
-
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-
-	glState.currentIBO = NULL;
-
-	GL_CheckErrors();
-
-	return ibo;
-}
-
-/*
-============
-R_CreateIBO2
-============
-*/
-IBO_t          *R_CreateIBO2(const char *name, int numIndexes, glIndex_t * inIndexes, vboUsage_t usage)
-{
-	IBO_t          *ibo;
-	int             i;
-
-	glIndex_t       *indexes;
-	int             indexesSize;
-
-	int				glUsage;
-
-	switch (usage)
-	{
-		case VBO_USAGE_STATIC:
-			glUsage = GL_STATIC_DRAW_ARB;
-			break;
-
-		case VBO_USAGE_DYNAMIC:
-			glUsage = GL_DYNAMIC_DRAW_ARB;
-			break;
-
-		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
-			return NULL;
+		// feed vertex light directions
+		p = (uint32_t *)(data + dataOfs);
+		*p = R_VaoPackNormal(verts[i].lightdir);
+		dataOfs += sizeof(uint32_t);
 	}
 
-	if(!numIndexes)
-		return NULL;
+	vao->vertexesSize = dataSize;
 
-	if(strlen(name) >= MAX_QPATH)
-	{
-		ri.Error(ERR_DROP, "R_CreateIBO2: \"%s\" is too long", name);
-	}
+	qglGenBuffersARB(1, &vao->vertexesVBO);
 
-	if ( tr.numIBOs == MAX_IBOS ) {
-		ri.Error( ERR_DROP, "R_CreateIBO2: MAX_IBOS hit");
-	}
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
+	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vao->vertexesSize, data, glUsage);
 
-	R_IssuePendingRenderCommands();
 
-	ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
-	tr.numIBOs++;
+	// create IBO
+	vao->indexesSize = numIndexes * sizeof(glIndex_t);
 
-	Q_strncpyz(ibo->name, name, sizeof(ibo->name));
+	qglGenBuffersARB(1, &vao->indexesIBO);
 
-	indexesSize = numIndexes * sizeof(glIndex_t);
-	indexes = ri.Hunk_AllocateTempMemory(indexesSize);
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesIBO);
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesSize, indexes, glUsage);
 
-	for(i = 0; i < numIndexes; i++)
-	{
-		indexes[i] = inIndexes[i];
-	}
-
-	ibo->indexesSize = indexesSize;
 
-	qglGenBuffersARB(1, &ibo->indexesVBO);
+	Vao_SetVertexPointers(vao);
 
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
-	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
 
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-
-	glState.currentIBO = NULL;
+	glState.currentVao = vao;
 
 	GL_CheckErrors();
 
-	ri.Hunk_FreeTempMemory(indexes);
+	ri.Hunk_FreeTempMemory(data);
 
-	return ibo;
+	return vao;
 }
 
+
 /*
 ============
-R_BindVBO
+R_BindVao
 ============
 */
-void R_BindVBO(VBO_t * vbo)
+void R_BindVao(vao_t * vao)
 {
-	if(!vbo)
+	if(!vao)
 	{
-		//R_BindNullVBO();
-		ri.Error(ERR_DROP, "R_BindNullVBO: NULL vbo");
+		//R_BindNullVao();
+		ri.Error(ERR_DROP, "R_BindVao: NULL vao");
 		return;
 	}
 
 	if(r_logFile->integer)
 	{
 		// don't just call LogComment, or we will get a call to va() every frame!
-		GLimp_LogComment(va("--- R_BindVBO( %s ) ---\n", vbo->name));
+		GLimp_LogComment(va("--- R_BindVao( %s ) ---\n", vao->name));
 	}
 
-	if(glState.currentVBO != vbo)
+	if(glState.currentVao != vao)
 	{
-		glState.currentVBO = vbo;
-		glState.vertexAttribPointersSet = 0;
+		glState.currentVao = vao;
 
 		glState.vertexAttribsInterpolation = 0;
 		glState.vertexAttribsOldFrame = 0;
 		glState.vertexAttribsNewFrame = 0;
 		glState.vertexAnimation = qfalse;
+		backEnd.pc.c_vaoBinds++;
 
-		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
-
-		backEnd.pc.c_vboVertexBuffers++;
-	}
-}
+		if (glRefConfig.vertexArrayObject)
+		{
+			qglBindVertexArrayARB(vao->vao);
 
-/*
-============
-R_BindNullVBO
-============
-*/
-void R_BindNullVBO(void)
-{
-	GLimp_LogComment("--- R_BindNullVBO ---\n");
+			// why you no save GL_ELEMENT_ARRAY_BUFFER binding, Intel?
+			if (1)
+				qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO);
+		}
+		else
+		{
+			qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
+			qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesIBO);
 
-	if(glState.currentVBO)
-	{
-		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
-		glState.currentVBO = NULL;
+			Vao_SetVertexPointers(vao);
+		}
 	}
-
-	GL_CheckErrors();
 }
 
 /*
 ============
-R_BindIBO
+R_BindNullVao
 ============
 */
-void R_BindIBO(IBO_t * ibo)
+void R_BindNullVao(void)
 {
-	if(!ibo)
-	{
-		//R_BindNullIBO();
-		ri.Error(ERR_DROP, "R_BindIBO: NULL ibo");
-		return;
-	}
+	GLimp_LogComment("--- R_BindNullVao ---\n");
 
-	if(r_logFile->integer)
+	if(glState.currentVao)
 	{
-		// don't just call LogComment, or we will get a call to va() every frame!
-		GLimp_LogComment(va("--- R_BindIBO( %s ) ---\n", ibo->name));
-	}
-
-	if(glState.currentIBO != ibo)
-	{
-		qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
-
-		glState.currentIBO = ibo;
+		if (glRefConfig.vertexArrayObject)
+		{
+			qglBindVertexArrayARB(0);
 
-		backEnd.pc.c_vboIndexBuffers++;
+			// why you no save GL_ELEMENT_ARRAY_BUFFER binding, Intel?
+			if (1) qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+		}
+		else
+		{
+			qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
+			qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+		}
+		glState.currentVao = NULL;
 	}
-}
 
-/*
-============
-R_BindNullIBO
-============
-*/
-void R_BindNullIBO(void)
-{
-	GLimp_LogComment("--- R_BindNullIBO ---\n");
-
-	if(glState.currentIBO)
-	{
-		qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-		glState.currentIBO = NULL;
-		glState.vertexAttribPointersSet = 0;
-	}
+	GL_CheckErrors();
 }
 
+
 /*
 ============
-R_InitVBOs
+R_InitVaos
 ============
 */
-void R_InitVBOs(void)
+void R_InitVaos(void)
 {
-	int             dataSize;
+	int             vertexesSize, indexesSize;
 	int             offset;
 
-	ri.Printf(PRINT_ALL, "------- R_InitVBOs -------\n");
+	ri.Printf(PRINT_ALL, "------- R_InitVaos -------\n");
 
-	tr.numVBOs = 0;
-	tr.numIBOs = 0;
+	tr.numVaos = 0;
 
-	dataSize  = sizeof(tess.xyz[0]);
-	dataSize += sizeof(tess.normal[0]);
+	vertexesSize  = sizeof(tess.xyz[0]);
+	vertexesSize += sizeof(tess.normal[0]);
 #ifdef USE_VERT_TANGENT_SPACE
-	dataSize += sizeof(tess.tangent[0]);
+	vertexesSize += sizeof(tess.tangent[0]);
 #endif
-	dataSize += sizeof(tess.vertexColors[0]);
-	dataSize += sizeof(tess.texCoords[0][0]) * 2;
-	dataSize += sizeof(tess.lightdir[0]);
-	dataSize *= SHADER_MAX_VERTEXES;
+	vertexesSize += sizeof(tess.vertexColors[0]);
+	vertexesSize += sizeof(tess.texCoords[0][0]) * 2;
+	vertexesSize += sizeof(tess.lightdir[0]);
+	vertexesSize *= SHADER_MAX_VERTEXES;
 
-	tess.vbo = R_CreateVBO("tessVertexArray_VBO", NULL, dataSize, VBO_USAGE_DYNAMIC);
+	indexesSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES;
+
+	tess.vao = R_CreateVao("tessVertexArray_VAO", NULL, vertexesSize, NULL, indexesSize, VAO_USAGE_DYNAMIC);
 
 	offset = 0;
 
-	tess.vbo->ofs_xyz         = offset; offset += sizeof(tess.xyz[0])              * SHADER_MAX_VERTEXES;
-	tess.vbo->ofs_normal      = offset; offset += sizeof(tess.normal[0])           * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].enabled = 1;
+#ifdef USE_VERT_TANGENT_SPACE
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].enabled = 1;
+#endif
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].count = 3;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].count = 4;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].count = 4;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].count = 2;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].count = 2;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].count = 4;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].type = glRefConfig.packedNormalDataType;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].type = glRefConfig.packedNormalDataType;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = glRefConfig.packedNormalDataType;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].normalized = GL_TRUE;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].normalized = GL_TRUE;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].offset = offset; offset += sizeof(tess.xyz[0])              * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].offset = offset; offset += sizeof(tess.normal[0])           * SHADER_MAX_VERTEXES;
 #ifdef USE_VERT_TANGENT_SPACE
-	tess.vbo->ofs_tangent     = offset; offset += sizeof(tess.tangent[0])          * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].offset = offset; offset += sizeof(tess.tangent[0])          * SHADER_MAX_VERTEXES;
 #endif
 	// these next two are actually interleaved
-	tess.vbo->ofs_st          = offset; 
-	tess.vbo->ofs_lightmap    = offset + sizeof(tess.texCoords[0][0]);
-	                                    offset += sizeof(tess.texCoords[0][0]) * 2 * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].offset = offset; 
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].offset = offset + sizeof(tess.texCoords[0][0]);
+	                                                              offset += sizeof(tess.texCoords[0][0]) * 2 * SHADER_MAX_VERTEXES;
 
-	tess.vbo->ofs_vertexcolor = offset; offset += sizeof(tess.vertexColors[0])     * SHADER_MAX_VERTEXES;
-	tess.vbo->ofs_lightdir    = offset;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].offset = offset; offset += sizeof(tess.vertexColors[0])     * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = offset;
 
-	tess.vbo->stride_xyz         = sizeof(tess.xyz[0]);
-	tess.vbo->stride_normal      = sizeof(tess.normal[0]);
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].stride = sizeof(tess.xyz[0]);
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].stride = sizeof(tess.normal[0]);
 #ifdef USE_VERT_TANGENT_SPACE
-	tess.vbo->stride_tangent     = sizeof(tess.tangent[0]);
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].stride = sizeof(tess.tangent[0]);
 #endif
-	tess.vbo->stride_vertexcolor = sizeof(tess.vertexColors[0]);
-	tess.vbo->stride_st          = sizeof(tess.texCoords[0][0]) * 2;
-	tess.vbo->stride_lightmap    = sizeof(tess.texCoords[0][0]) * 2;
-	tess.vbo->stride_lightdir    = sizeof(tess.lightdir[0]);
-
-	dataSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].stride = sizeof(tess.vertexColors[0]);
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].stride = sizeof(tess.texCoords[0][0]) * 2;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].stride = sizeof(tess.texCoords[0][0]) * 2;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = sizeof(tess.lightdir[0]);
+
+	tess.attribPointers[ATTR_INDEX_POSITION]       = tess.xyz;
+	tess.attribPointers[ATTR_INDEX_TEXCOORD]       = tess.texCoords;
+	tess.attribPointers[ATTR_INDEX_NORMAL]         = tess.normal;
+#ifdef USE_VERT_TANGENT_SPACE
+	tess.attribPointers[ATTR_INDEX_TANGENT]        = tess.tangent;
+#endif
+	tess.attribPointers[ATTR_INDEX_COLOR]          = tess.vertexColors;
+	tess.attribPointers[ATTR_INDEX_LIGHTDIRECTION] = tess.lightdir;
 
-	tess.ibo = R_CreateIBO("tessVertexArray_IBO", NULL, dataSize, VBO_USAGE_DYNAMIC);
+	Vao_SetVertexPointers(tess.vao);
 
-	R_BindNullVBO();
-	R_BindNullIBO();
+	R_BindNullVao();
 
 	GL_CheckErrors();
 }
 
 /*
 ============
-R_ShutdownVBOs
+R_ShutdownVaos
 ============
 */
-void R_ShutdownVBOs(void)
+void R_ShutdownVaos(void)
 {
 	int             i;
-	VBO_t          *vbo;
-	IBO_t          *ibo;
-
-	ri.Printf(PRINT_ALL, "------- R_ShutdownVBOs -------\n");
+	vao_t          *vao;
 
-	R_BindNullVBO();
-	R_BindNullIBO();
+	ri.Printf(PRINT_ALL, "------- R_ShutdownVaos -------\n");
 
+	R_BindNullVao();
 
-	for(i = 0; i < tr.numVBOs; i++)
+	for(i = 0; i < tr.numVaos; i++)
 	{
-		vbo = tr.vbos[i];
+		vao = tr.vaos[i];
+
+		if(vao->vao)
+			qglDeleteVertexArraysARB(1, &vao->vao);
 
-		if(vbo->vertexesVBO)
+		if(vao->vertexesVBO)
 		{
-			qglDeleteBuffersARB(1, &vbo->vertexesVBO);
+			qglDeleteBuffersARB(1, &vao->vertexesVBO);
 		}
 
-		//ri.Free(vbo);
-	}
-
-	for(i = 0; i < tr.numIBOs; i++)
-	{
-		ibo = tr.ibos[i];
-
-		if(ibo->indexesVBO)
+		if(vao->indexesIBO)
 		{
-			qglDeleteBuffersARB(1, &ibo->indexesVBO);
+			qglDeleteBuffersARB(1, &vao->indexesIBO);
 		}
-
-		//ri.Free(ibo);
 	}
 
-	tr.numVBOs = 0;
-	tr.numIBOs = 0;
+	tr.numVaos = 0;
 }
 
 /*
 ============
-R_VBOList_f
+R_VaoList_f
 ============
 */
-void R_VBOList_f(void)
+void R_VaoList_f(void)
 {
 	int             i;
-	VBO_t          *vbo;
-	IBO_t          *ibo;
+	vao_t          *vao;
 	int             vertexesSize = 0;
 	int             indexesSize = 0;
 
 	ri.Printf(PRINT_ALL, " size          name\n");
 	ri.Printf(PRINT_ALL, "----------------------------------------------------------\n");
 
-	for(i = 0; i < tr.numVBOs; i++)
+	for(i = 0; i < tr.numVaos; i++)
 	{
-		vbo = tr.vbos[i];
+		vao = tr.vaos[i];
 
-		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vbo->vertexesSize / (1024 * 1024),
-				  (vbo->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vbo->name);
+		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->vertexesSize / (1024 * 1024),
+				  (vao->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name);
 
-		vertexesSize += vbo->vertexesSize;
+		vertexesSize += vao->vertexesSize;
 	}
 
-	for(i = 0; i < tr.numIBOs; i++)
+	for(i = 0; i < tr.numVaos; i++)
 	{
-		ibo = tr.ibos[i];
+		vao = tr.vaos[i];
 
-		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", ibo->indexesSize / (1024 * 1024),
-				  (ibo->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), ibo->name);
+		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->indexesSize / (1024 * 1024),
+				  (vao->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name);
 
-		indexesSize += ibo->indexesSize;
+		indexesSize += vao->indexesSize;
 	}
 
-	ri.Printf(PRINT_ALL, " %i total VBOs\n", tr.numVBOs);
+	ri.Printf(PRINT_ALL, " %i total VAOs\n", tr.numVaos);
 	ri.Printf(PRINT_ALL, " %d.%02d MB total vertices memory\n", vertexesSize / (1024 * 1024),
 			  (vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
-
-	ri.Printf(PRINT_ALL, " %i total IBOs\n", tr.numIBOs);
 	ri.Printf(PRINT_ALL, " %d.%02d MB total triangle indices memory\n", indexesSize / (1024 * 1024),
 			  (indexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
 }
@@ -882,89 +622,61 @@ void R_VBOList_f(void)
 
 /*
 ==============
-RB_UpdateVBOs
+RB_UpdateTessVao
 
 Adapted from Tess_UpdateVBOs from xreal
 
-Update the default VBO to replace the client side vertex arrays
+Update the default VAO to replace the client side vertex arrays
 ==============
 */
-void RB_UpdateVBOs(unsigned int attribBits)
+void RB_UpdateTessVao(unsigned int attribBits)
 {
-	GLimp_LogComment("--- RB_UpdateVBOs ---\n");
+	GLimp_LogComment("--- RB_UpdateTessVao ---\n");
 
-	backEnd.pc.c_dynamicVboDraws++;
+	backEnd.pc.c_dynamicVaoDraws++;
 
-	// update the default VBO
-	if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES)
+	// update the default VAO
+	if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES && tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES)
 	{
-		R_BindVBO(tess.vbo);
+		int attribIndex;
 
-		// orphan old buffer so we don't stall on it
-		qglBufferDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->vertexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
+		R_BindVao(tess.vao);
 
-		if(attribBits & ATTR_BITS)
+		// these may not be bound if we're using VAOs
+		if (glRefConfig.vertexArrayObject)
 		{
-			if(attribBits & ATTR_POSITION)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_xyz, tess.numVertexes * sizeof(tess.xyz[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz,         tess.numVertexes * sizeof(tess.xyz[0]),              tess.xyz);
-			}
+			qglBindBufferARB(GL_ARRAY_BUFFER_ARB, tess.vao->vertexesVBO);
+			qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tess.vao->indexesIBO);
+		}
 
-			if(attribBits & ATTR_TEXCOORD || attribBits & ATTR_LIGHTCOORD)
-			{
-				// these are interleaved, so we update both if either need it
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_st, tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2);
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st,          tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords);
-			}
+		// orphan old vertex buffer so we don't stall on it
+		qglBufferDataARB(GL_ARRAY_BUFFER_ARB, tess.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
 
-			if(attribBits & ATTR_NORMAL)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_normal, tess.numVertexes * sizeof(tess.normal[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal,      tess.numVertexes * sizeof(tess.normal[0]),           tess.normal);
-			}
+		// if nothing to set, set everything
+		if(!(attribBits & ATTR_BITS))
+			attribBits = ATTR_BITS;
 
-#ifdef USE_VERT_TANGENT_SPACE
-			if(attribBits & ATTR_TANGENT)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_tangent, tess.numVertexes * sizeof(tess.tangent[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent,     tess.numVertexes * sizeof(tess.tangent[0]),          tess.tangent);
-			}
-#endif
+		if(attribBits & ATTR_TEXCOORD || attribBits & ATTR_LIGHTCOORD)
+		{
+			// these are interleaved, so we update both if either need it
+			// this translates to updating ATTR_TEXCOORD twice as large as it needs
+			attribBits &= ~ATTR_LIGHTCOORD;
+			attribBits |= ATTR_TEXCOORD;
+		}
 
-			if(attribBits & ATTR_COLOR)
+		for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
+		{
+			if (attribBits & (1 << attribIndex))
 			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]),     tess.vertexColors);
-			}
+				vaoAttrib_t *vAtb = &tess.vao->attribs[attribIndex];
 
-			if(attribBits & ATTR_LIGHTDIRECTION)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_lightdir, tess.numVertexes * sizeof(tess.lightdir[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir,    tess.numVertexes * sizeof(tess.lightdir[0]),         tess.lightdir);
+				// note: tess has a VBO where stride == size
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, vAtb->offset, tess.numVertexes * vAtb->stride, tess.attribPointers[attribIndex]);
 			}
 		}
-		else
-		{
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz,         tess.numVertexes * sizeof(tess.xyz[0]),              tess.xyz);
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st,          tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords);
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal,      tess.numVertexes * sizeof(tess.normal[0]),           tess.normal);
-#ifdef USE_VERT_TANGENT_SPACE
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent,     tess.numVertexes * sizeof(tess.tangent[0]),          tess.tangent);
-#endif
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]),     tess.vertexColors);
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir,    tess.numVertexes * sizeof(tess.lightdir[0]),         tess.lightdir);
-		}
-
-	}
-
-	// update the default IBO
-	if(tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES)
-	{
-		R_BindIBO(tess.ibo);
 
-		// orphan old buffer so we don't stall on it
-		qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tess.ibo->indexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
+		// orphan old index buffer so we don't stall on it
+		qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tess.vao->indexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
 
 		qglBufferSubDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes);
 	}
diff --git a/MP/code/rend2/tr_world.c b/MP/code/rend2/tr_world.c
index aff3585..7779566 100644
--- a/MP/code/rend2/tr_world.c
+++ b/MP/code/rend2/tr_world.c
@@ -219,7 +219,7 @@ static int R_DlightSurface( msurface_t *surf, int dlightBits ) {
 		case SF_FACE:
 		case SF_GRID:
 		case SF_TRIANGLES:
-		case SF_VBO_MESH:
+		case SF_VAO_MESH:
 			((srfBspSurface_t *)surf->data)->dlightBits = dlightBits;
 			break;
 
@@ -305,7 +305,7 @@ static int R_PshadowSurface( msurface_t *surf, int pshadowBits ) {
 		case SF_FACE:
 		case SF_GRID:
 		case SF_TRIANGLES:
-		case SF_VBO_MESH:
+		case SF_VAO_MESH:
 			((srfBspSurface_t *)surf->data)->pshadowBits = pshadowBits;
 			break;
 
diff --git a/MP/code/renderer/qgl.h b/MP/code/renderer/qgl.h
index 1628a6b..cae6282 100644
--- a/MP/code/renderer/qgl.h
+++ b/MP/code/renderer/qgl.h
@@ -484,6 +484,16 @@ extern void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
 #define GL_TEXTURE_CUBE_MAP_SEAMLESS               0x884F
 #endif
 
+// GL_ARB_vertex_array_object
+extern void (APIENTRY * qglBindVertexArrayARB)(GLuint array);
+extern void (APIENTRY * qglDeleteVertexArraysARB)(GLsizei n, const GLuint *arrays);
+extern void (APIENTRY * qglGenVertexArraysARB)(GLsizei n, GLuint *arrays);
+extern GLboolean (APIENTRY * qglIsVertexArrayARB)(GLuint array);
+#ifndef GL_ARB_vertex_array_object
+#define GL_ARB_vertex_array_object
+#define GL_VERTEX_ARRAY_BINDING_ARB                0x85B5
+#endif
+
 #if defined(WIN32)
 // WGL_ARB_create_context
 #ifndef WGL_ARB_create_context
diff --git a/MP/rend2-readme.txt b/MP/rend2-readme.txt
index 1d39ad8..fc2fd10 100644
--- a/MP/rend2-readme.txt
+++ b/MP/rend2-readme.txt
@@ -549,7 +549,7 @@ There are currently two ways to use this in your own (and other people's) maps.
         surfaceparm nolightmap
         surfaceparm sky
         q3map_sunExt 240 238 200 100 195 35 3 16
-        q3gl2_sun 240 238 200 50 195 35 3 1.0 0.2
+	q3gl2_sun 240 238 200 50 195 35 1.0 0.2
         q3map_skylight 50 16
         q3map_lightimage $whiteimage
 
@@ -572,7 +572,7 @@ There are currently two ways to use this in your own (and other people's) maps.
         surfaceparm noimpact
         surfaceparm nolightmap
         surfaceparm sky
-        q3gl2_sun 240 238 200 50 195 35 3 0.5 0.2
+	q3gl2_sun 240 238 200 50 195 35 0.5 0.2
         q3map_skylight 50 16
         q3map_lightimage $whiteimage
 
diff --git a/SP/code/rend2/qgl.h b/SP/code/rend2/qgl.h
index bbd7a4b..42a7618 100644
--- a/SP/code/rend2/qgl.h
+++ b/SP/code/rend2/qgl.h
@@ -467,6 +467,16 @@ extern void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
 #define GL_TEXTURE_CUBE_MAP_SEAMLESS               0x884F
 #endif
 
+// GL_ARB_vertex_array_object
+extern void (APIENTRY * qglBindVertexArrayARB)(GLuint array);
+extern void (APIENTRY * qglDeleteVertexArraysARB)(GLsizei n, const GLuint *arrays);
+extern void (APIENTRY * qglGenVertexArraysARB)(GLsizei n, GLuint *arrays);
+extern GLboolean (APIENTRY * qglIsVertexArrayARB)(GLuint array);
+#ifndef GL_ARB_vertex_array_object
+#define GL_ARB_vertex_array_object
+#define GL_VERTEX_ARRAY_BINDING_ARB                0x85B5
+#endif
+
 #if defined(WIN32)
 // WGL_ARB_create_context
 #ifndef WGL_ARB_create_context
diff --git a/SP/code/rend2/tr_animation.c b/SP/code/rend2/tr_animation.c
index 6ed9c6f..e4f27ca 100644
--- a/SP/code/rend2/tr_animation.c
+++ b/SP/code/rend2/tr_animation.c
@@ -1198,7 +1198,7 @@ void RB_SurfaceAnim( mdsSurface_t *surface ) {
 		}
 		LocalMatrixTransformVector( v->normal, bones[v->weights[0].boneIndex].matrix, newNormal );
 		
-		*tempNormal = R_VboPackNormal(newNormal);
+		*tempNormal = R_VaoPackNormal(newNormal);
 
 		tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
 		tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
@@ -1748,7 +1748,7 @@ void RB_MDRSurfaceAnim( mdrSurface_t *surface )
 		tess.xyz[baseVertex + j][1] = tempVert[1];
 		tess.xyz[baseVertex + j][2] = tempVert[2];
 
-		tess.normal[baseVertex + j] = R_VboPackNormal(tempNormal);
+		tess.normal[baseVertex + j] = R_VaoPackNormal(tempNormal);
 
 		tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
 		tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
diff --git a/SP/code/rend2/tr_backend.c b/SP/code/rend2/tr_backend.c
index 25933f6..3989b19 100644
--- a/SP/code/rend2/tr_backend.c
+++ b/SP/code/rend2/tr_backend.c
@@ -127,8 +127,6 @@ void GL_Cull( int cullType ) {
 		return;
 	}
 
-	glState.faceCulling = cullType;
-
 	if ( cullType == CT_TWO_SIDED )
 	{
 		qglDisable( GL_CULL_FACE );
@@ -136,7 +134,11 @@ void GL_Cull( int cullType ) {
 	else
 	{
 		qboolean cullFront;
-		qglEnable( GL_CULL_FACE );
+
+		if ( glState.faceCulling == CT_TWO_SIDED )
+		{
+			qglEnable( GL_CULL_FACE );
+		}
 
 		cullFront = (cullType == CT_FRONT_SIDED);
 		if ( backEnd.viewParms.isMirror )
@@ -148,8 +150,13 @@ void GL_Cull( int cullType ) {
 			cullFront = !cullFront;
 		}
 
-		qglCullFace( cullFront ? GL_FRONT : GL_BACK );
+		if (glState.faceCullFront != cullFront)
+			qglCullFace( cullFront ? GL_FRONT : GL_BACK );
+
+		glState.faceCullFront = cullFront;
 	}
+
+	glState.faceCulling = cullType;
 }
 
 /*
@@ -219,9 +226,26 @@ void GL_State( unsigned long stateBits ) {
 	// check blend bits
 	//
 	if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) {
-		GLenum srcFactor = GL_ONE, dstFactor = GL_ONE;
+		uint32_t oldState = glState.glStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+		uint32_t newState = stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+		uint32_t storedState = glState.storedGlState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+
+		if (oldState == 0)
+ 		{
+			qglEnable( GL_BLEND );
+		}
+		else if (newState == 0)
+		{
+			qglDisable( GL_BLEND );
+		}
+
+		if (newState != 0 && storedState != newState)
+		{
+			GLenum srcFactor = GL_ONE, dstFactor = GL_ONE;
+
+			glState.storedGlState &= ~( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+			glState.storedGlState |= newState;
 
-		if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) {
 			switch ( stateBits & GLS_SRCBLEND_BITS )
 			{
 			case GLS_SRCBLEND_ZERO:
@@ -287,11 +311,7 @@ void GL_State( unsigned long stateBits ) {
 				break;
 			}
 
-			qglEnable( GL_BLEND );
 			qglBlendFunc( srcFactor, dstFactor );
-		} else
-		{
-			qglDisable( GL_BLEND );
 		}
 	}
 
@@ -335,26 +355,36 @@ void GL_State( unsigned long stateBits ) {
 	// alpha test
 	//
 	if ( diff & GLS_ATEST_BITS ) {
-		switch ( stateBits & GLS_ATEST_BITS )
+		uint32_t oldState = glState.glStateBits & GLS_ATEST_BITS;
+		uint32_t newState = stateBits & GLS_ATEST_BITS;
+		uint32_t storedState = glState.storedGlState & GLS_ATEST_BITS;
+
+		if (oldState == 0)
 		{
-		case 0:
-			qglDisable( GL_ALPHA_TEST );
-			break;
-		case GLS_ATEST_GT_0:
-			qglEnable( GL_ALPHA_TEST );
-			qglAlphaFunc( GL_GREATER, 0.0f );
-			break;
-		case GLS_ATEST_LT_80:
-			qglEnable( GL_ALPHA_TEST );
-			qglAlphaFunc( GL_LESS, 0.5f );
-			break;
-		case GLS_ATEST_GE_80:
-			qglEnable( GL_ALPHA_TEST );
-			qglAlphaFunc( GL_GEQUAL, 0.5f );
-			break;
-		default:
-			assert( 0 );
-			break;
+			qglEnable(GL_ALPHA_TEST);
+		}
+		else if (newState == 0)
+		{
+			qglDisable(GL_ALPHA_TEST);
+		}
+
+		if (newState != 0 && storedState != newState)
+		{
+			glState.storedGlState &= ~GLS_ATEST_BITS;
+			glState.storedGlState |= newState;
+
+			switch ( newState )
+			{
+			case GLS_ATEST_GT_0:
+				qglAlphaFunc( GL_GREATER, 0.0f );
+				break;
+			case GLS_ATEST_LT_80:
+				qglAlphaFunc( GL_LESS, 0.5f );
+				break;
+			case GLS_ATEST_GE_80:
+				qglAlphaFunc( GL_GEQUAL, 0.5f );
+				break;
+			}
 		}
 	}
 
@@ -391,6 +421,7 @@ static void RB_Hyperspace( void ) {
 	c = ( backEnd.refdef.time & 255 ) / 255.0f;
 	qglClearColor( c, c, c, 1 );
 	qglClear( GL_COLOR_BUFFER_BIT );
+	qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 
 	backEnd.isHyperspace = qtrue;
 }
@@ -499,7 +530,7 @@ void RB_BeginDrawingView( void ) {
 					qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] );
 				} else {
 //					qglClearColor ( 1.0, 0.0, 0.0, 1.0 );	// red clear for testing portal sky clear
-					qglClearColor( 0.5, 0.5, 0.5, 1.0 );
+//					qglClearColor( 0.5, 0.5, 0.5, 1.0 );
 				}
 			} else {                                                    // rendered sky (either clear color or draw quake sky)
 				if ( glfogsettings[FOG_PORTALVIEW].registered ) {
@@ -545,7 +576,7 @@ void RB_BeginDrawingView( void ) {
 				qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] );
 			} else {
 //				qglClearColor ( 0.0, 0.0, 1.0, 1.0 );	// blue clear for testing world sky clear
-				qglClearColor( 0.5, 0.5, 0.5, 1.0 );
+//				qglClearColor( 0.5, 0.5, 0.5, 1.0 );
 			}
 		} else {        // world scene, no portal sky, not fastsky, clear color if fog says to, otherwise, just set the clearcolor
 			if ( glfogsettings[FOG_CURRENT].registered ) { // try to clear fastsky with current fog color
@@ -562,7 +593,6 @@ void RB_BeginDrawingView( void ) {
 	if (tr.renderCubeFbo && backEnd.viewParms.targetFbo == tr.renderCubeFbo)
 	{
 		clearBits |= GL_COLOR_BUFFER_BIT;
-		qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
 	}
 
 	if ( clearBits ) {
@@ -580,6 +610,7 @@ void RB_BeginDrawingView( void ) {
 	}
 
 	glState.faceCulling = -1;       // force face culling to set next time
+	glState.faceCullFront = -1;     // same as above
 
 	// we will only draw a sun if there was sky rendered in this view
 	backEnd.skyRenderedThisView = qfalse;
@@ -715,7 +746,7 @@ void RB_ZombieFXProcessNewHits( trZombieFleshHitverts_t *fleshHitVerts, int oldN
 				continue;
 			}
 
-			R_VboUnpackNormal(fNormTrav, *normTrav);
+			R_VaoUnpackNormal(fNormTrav, *normTrav);
 
 			// if this normal faces the wrong way, reject it
 			if ( DotProduct( fNormTrav, hitDir ) > 0 ) {
@@ -821,7 +852,7 @@ void RB_ZombieFXDecompose( int oldNumVerts, int numSurfVerts, float deltaTimeSca
 			vertColors[3] -= (byte)alpha;
 		}
 
-		R_VboUnpackNormal(fNorm, *norm);
+		R_VaoUnpackNormal(fNorm, *norm);
 
 		// skin shrinks with age
 		VectorMA( xyz, -2.0 * deltaTimeScale, fNorm, xyz );
diff --git a/SP/code/rend2/tr_bsp.c b/SP/code/rend2/tr_bsp.c
index 4b616df..e899aa8 100644
--- a/SP/code/rend2/tr_bsp.c
+++ b/SP/code/rend2/tr_bsp.c
@@ -2065,10 +2065,10 @@ static void CopyVert(const srfVert_t * in, srfVert_t * out)
 
 /*
 ===============
-R_CreateWorldVBOs
+R_CreateWorldVaos
 ===============
 */
-static void R_CreateWorldVBOs(void)
+static void R_CreateWorldVaos(void)
 {
 	int             i, j, k;
 
@@ -2082,8 +2082,7 @@ static void R_CreateWorldVBOs(void)
 	msurface_t   *surface, **firstSurf, **lastSurf, **currSurf;
 	msurface_t  **surfacesSorted;
 
-	VBO_t *vbo;
-	IBO_t *ibo;
+	vao_t *vao;
 
 	int maxVboSize = 4 * 1024 * 1024;
 
@@ -2199,7 +2198,7 @@ static void R_CreateWorldVBOs(void)
 	{
 		int currVboSize;
 
-		// Find range of surfaces to place in a vbo/ibo by:
+		// Find range of surfaces to place in a VAO by:
 		// - Collecting a number of surfaces which fit under maxVboSize, or
 		// - All the surfaces with a single shader which go over maxVboSize
 		currVboSize = 0;
@@ -2238,7 +2237,7 @@ static void R_CreateWorldVBOs(void)
 			numSurfaces++;
 		}
 
-		ri.Printf(PRINT_ALL, "...calculating world VBO %d ( %i verts %i tris )\n", k, numVerts, numIndexes / 3);
+		ri.Printf(PRINT_ALL, "...calculating world VAO %d ( %i verts %i tris )\n", k, numVerts, numIndexes / 3);
 
 		// create arrays
 		verts = ri.Hunk_AllocateTempMemory(numVerts * sizeof(srfVert_t));
@@ -2271,25 +2270,14 @@ static void R_CreateWorldVBOs(void)
 			}
 		}
 
-#ifdef USE_VERT_TANGENT_SPACE
-		vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", k), numVerts, verts,
-									   ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT |
-									   ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
-#else
-		vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", k), numVerts, verts,
-									   ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD |
-									   ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
-#endif
-
-		ibo = R_CreateIBO2(va("staticBspModel0_IBO %i", k), numIndexes, indexes, VBO_USAGE_STATIC);
+		vao = R_CreateVao2(va("staticBspModel%i_VAO", k), numVerts, verts, numIndexes, indexes);
 
-		// point bsp surfaces to VBO
+		// point bsp surfaces to VAO
 		for (currSurf = firstSurf; currSurf < lastSurf; currSurf++)
 		{
 			srfBspSurface_t *bspSurf = (srfBspSurface_t *) (*currSurf)->data;
 
-			bspSurf->vbo = vbo;
-			bspSurf->ibo = ibo;
+			bspSurf->vao = vao;
 		}
 
 		ri.Hunk_FreeTempMemory(indexes);
@@ -2353,7 +2341,7 @@ static void R_CreateWorldVBOs(void)
 		mergedSurf = s_worldData.mergedSurfaces;
 		for(firstSurf = lastSurf = surfacesSorted; firstSurf < surfacesSorted + numSortedSurfaces; firstSurf = lastSurf)
 		{
-			srfBspSurface_t *bspSurf, *vboSurf;
+			srfBspSurface_t *bspSurf, *vaoSurf;
 
 			for ( lastSurf++ ; lastSurf < surfacesSorted + numSortedSurfaces; lastSurf++)
 			{
@@ -2377,35 +2365,34 @@ static void R_CreateWorldVBOs(void)
 
 			bspSurf = (srfBspSurface_t *)(*firstSurf)->data;
 
-			vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
-			memset(vboSurf, 0, sizeof(*vboSurf));
-			vboSurf->surfaceType = SF_VBO_MESH;
+			vaoSurf = ri.Hunk_Alloc(sizeof(*vaoSurf), h_low);
+			memset(vaoSurf, 0, sizeof(*vaoSurf));
+			vaoSurf->surfaceType = SF_VAO_MESH;
 
-			vboSurf->vbo = bspSurf->vbo;
-			vboSurf->ibo = bspSurf->ibo;
+			vaoSurf->vao = bspSurf->vao;
 
-			vboSurf->firstIndex = bspSurf->firstIndex;
-			vboSurf->minIndex = bspSurf->minIndex;
-			vboSurf->maxIndex = bspSurf->maxIndex;
+			vaoSurf->firstIndex = bspSurf->firstIndex;
+			vaoSurf->minIndex = bspSurf->minIndex;
+			vaoSurf->maxIndex = bspSurf->maxIndex;
 
-			ClearBounds(vboSurf->cullBounds[0], vboSurf->cullBounds[1]);
+			ClearBounds(vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]);
 			for (currSurf = firstSurf; currSurf < lastSurf; currSurf++)
 			{
 				srfBspSurface_t *currBspSurf = (srfBspSurface_t *)(*currSurf)->data;
 
-				vboSurf->numVerts   += currBspSurf->numVerts;
-				vboSurf->numIndexes += currBspSurf->numIndexes;
-				vboSurf->minIndex = MIN(vboSurf->minIndex, currBspSurf->minIndex);
-				vboSurf->maxIndex = MAX(vboSurf->maxIndex, currBspSurf->maxIndex);
-				AddPointToBounds((*currSurf)->cullinfo.bounds[0], vboSurf->cullBounds[0], vboSurf->cullBounds[1]);
-				AddPointToBounds((*currSurf)->cullinfo.bounds[1], vboSurf->cullBounds[0], vboSurf->cullBounds[1]);
+				vaoSurf->numVerts   += currBspSurf->numVerts;
+				vaoSurf->numIndexes += currBspSurf->numIndexes;
+				vaoSurf->minIndex = MIN(vaoSurf->minIndex, currBspSurf->minIndex);
+				vaoSurf->maxIndex = MAX(vaoSurf->maxIndex, currBspSurf->maxIndex);
+				AddPointToBounds((*currSurf)->cullinfo.bounds[0], vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]);
+				AddPointToBounds((*currSurf)->cullinfo.bounds[1], vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]);
 			}
 
-			VectorCopy(vboSurf->cullBounds[0], mergedSurf->cullinfo.bounds[0]);
-			VectorCopy(vboSurf->cullBounds[1], mergedSurf->cullinfo.bounds[1]);
+			VectorCopy(vaoSurf->cullBounds[0], mergedSurf->cullinfo.bounds[0]);
+			VectorCopy(vaoSurf->cullBounds[1], mergedSurf->cullinfo.bounds[1]);
 
 			mergedSurf->cullinfo.type =  CULLINFO_BOX;
-			mergedSurf->data          =  (surfaceType_t *)vboSurf;
+			mergedSurf->data          =  (surfaceType_t *)vaoSurf;
 			mergedSurf->fogIndex      =  (*firstSurf)->fogIndex;
 			mergedSurf->cubemapIndex  =  (*firstSurf)->cubemapIndex;
 			mergedSurf->shader        =  (*firstSurf)->shader;
@@ -2436,7 +2423,7 @@ static void R_CreateWorldVBOs(void)
 	ri.Free(surfacesSorted);
 
 	endTime = ri.Milliseconds();
-	ri.Printf(PRINT_ALL, "world VBOs calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
+	ri.Printf(PRINT_ALL, "world VAOs calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
 }
 
 /*
@@ -2507,7 +2494,7 @@ static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) {
 //	R_InitSurfMemory();
 
 	// Two passes, allocate surfaces first, then load them full of data
-	// This ensures surfaces are close together to reduce L2 cache misses when using VBOs,
+	// This ensures surfaces are close together to reduce L2 cache misses when using VAOs,
 	// which don't actually use the verts and indexes
 	in = (void *)(fileBase + surfs->fileofs);
 	out = s_worldData.surfaces;
@@ -2629,7 +2616,7 @@ static void R_LoadSubmodels( lump_t *l ) {
 
 		if(i == 0)
 		{
-			// Add this for limiting VBO surface creation
+			// Add this for limiting VAO surface creation
 			s_worldData.numWorldSurfaces = out->numSurfaces;
 		}
 	}
@@ -3777,8 +3764,8 @@ void RE_LoadWorldMap( const char *name ) {
 		}
 	}
 
-	// create static VBOS from the world
-	R_CreateWorldVBOs();
+	// create static VAOS from the world
+	R_CreateWorldVaos();
 
 	s_worldData.dataSize = (byte *)ri.Hunk_Alloc( 0, h_low ) - startMarker;
 
@@ -3795,9 +3782,8 @@ void RE_LoadWorldMap( const char *name ) {
 
 //----(SA)	end
 
-	// make sure the VBO glState entries are safe
-	R_BindNullVBO();
-	R_BindNullIBO();
+	// make sure the VAO glState entry is safe
+	R_BindNullVao();
 
 	// Render all cubemaps
 	if (r_cubeMapping->integer && tr.numCubemaps)
diff --git a/SP/code/rend2/tr_cmds.c b/SP/code/rend2/tr_cmds.c
index b247ab0..6c9e4a7 100644
--- a/SP/code/rend2/tr_cmds.c
+++ b/SP/code/rend2/tr_cmds.c
@@ -73,8 +73,8 @@ void R_PerformanceCounters( void ) {
 	}
 	else if (r_speeds->integer == 7 )
 	{
-		ri.Printf( PRINT_ALL, "VBO draws: static %i dynamic %i\nMultidraws: %i merged %i\n",
-			backEnd.pc.c_staticVboDraws, backEnd.pc.c_dynamicVboDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged );
+		ri.Printf( PRINT_ALL, "VAO draws: static %i dynamic %i\nMultidraws: %i merged %i\n",
+			backEnd.pc.c_staticVaoDraws, backEnd.pc.c_dynamicVaoDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged );
 		ri.Printf( PRINT_ALL, "GLSL binds: %i  draws: gen %i light %i fog %i dlight %i\n",
 			backEnd.pc.c_glslShaderBinds, backEnd.pc.c_genericDraws, backEnd.pc.c_lightallDraws, backEnd.pc.c_fogDraws, backEnd.pc.c_dlightDraws);
 	}
@@ -531,7 +531,6 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
 				backEnd.colorMask[1] = GL_FALSE;
 				backEnd.colorMask[2] = GL_FALSE;
 				backEnd.colorMask[3] = GL_FALSE;
-				qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 
 				if (glRefConfig.framebufferObject)
 				{
diff --git a/SP/code/rend2/tr_extensions.c b/SP/code/rend2/tr_extensions.c
index 168e1e7..6ac8f83 100644
--- a/SP/code/rend2/tr_extensions.c
+++ b/SP/code/rend2/tr_extensions.c
@@ -178,6 +178,12 @@ void (APIENTRY * qglRenderbufferStorageMultisampleEXT)(GLenum target, GLsizei sa
 // GL_ARB_draw_buffers
 void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
 
+// GL_ARB_vertex_array_object
+void (APIENTRY * qglBindVertexArrayARB)(GLuint array);
+void (APIENTRY * qglDeleteVertexArraysARB)(GLsizei n, const GLuint *arrays);
+void (APIENTRY * qglGenVertexArraysARB)(GLsizei n, GLuint *arrays);
+GLboolean (APIENTRY * qglIsVertexArrayARB)(GLuint array);
+
 static qboolean GLimp_HaveExtension(const char *ext)
 {
 	const char *ptr = Q_stristr( (char *)qglGetString(GL_EXTENSIONS), ext );
@@ -682,4 +688,25 @@ void GLimp_InitExtraExtensions()
 
 	// use float lightmaps?
 	glRefConfig.floatLightmap = (glRefConfig.textureFloat && glRefConfig.halfFloatPixel && r_floatLightmap->integer && r_hdr->integer);
+
+	// GL_ARB_vertex_array_object
+	extension = "GL_ARB_vertex_array_object";
+	glRefConfig.vertexArrayObject = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglBindVertexArrayARB = (void *) SDL_GL_GetProcAddress("glBindVertexArray");
+		qglDeleteVertexArraysARB = (void *) SDL_GL_GetProcAddress("glDeleteVertexArrays");
+		qglGenVertexArraysARB = (void *) SDL_GL_GetProcAddress("glGenVertexArrays");
+		qglIsVertexArrayARB = (void *) SDL_GL_GetProcAddress("glIsVertexArray");
+
+		if (r_arb_vertex_array_object->integer)
+			glRefConfig.vertexArrayObject = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.vertexArrayObject ? 1 : 0], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
 }
diff --git a/SP/code/rend2/tr_fbo.c b/SP/code/rend2/tr_fbo.c
index 4070c6e..ba145e6 100644
--- a/SP/code/rend2/tr_fbo.c
+++ b/SP/code/rend2/tr_fbo.c
@@ -447,7 +447,6 @@ void FBO_Init(void)
 	if (tr.renderFbo)
 	{
 		FBO_Bind(tr.renderFbo);
-		qglClearColor( 1, 0, 0.5, 1 );
 		qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
 		FBO_Bind(NULL);
 	}
diff --git a/SP/code/rend2/tr_glsl.c b/SP/code/rend2/tr_glsl.c
index 00d0c19..26f5c58 100644
--- a/SP/code/rend2/tr_glsl.c
+++ b/SP/code/rend2/tr_glsl.c
@@ -537,10 +537,10 @@ static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_POSITION, "attr_Position");
 
 	if(attribs & ATTR_TEXCOORD)
-		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD0, "attr_TexCoord0");
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD, "attr_TexCoord0");
 
 	if(attribs & ATTR_LIGHTCOORD)
-		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD1, "attr_TexCoord1");
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_LIGHTCOORD, "attr_TexCoord1");
 
 //  if(attribs & ATTR_TEXCOORD2)
 //      qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2");
@@ -548,10 +548,8 @@ static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int
 //  if(attribs & ATTR_TEXCOORD3)
 //      qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD3, "attr_TexCoord3");
 
-#ifdef USE_VERT_TANGENT_SPACE
 	if(attribs & ATTR_TANGENT)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT, "attr_Tangent");
-#endif
 
 	if(attribs & ATTR_NORMAL)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL, "attr_Normal");
@@ -571,10 +569,8 @@ static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int
 	if(attribs & ATTR_NORMAL2)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL2, "attr_Normal2");
 
-#ifdef USE_VERT_TANGENT_SPACE
 	if(attribs & ATTR_TANGENT2)
 		qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT2, "attr_Tangent2");
-#endif
 
 	GLSL_LinkProgram(program->program);
 
@@ -1424,20 +1420,9 @@ void GLSL_ShutdownGPUShaders(void)
 
 	ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n");
 
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
-#ifdef USE_VERT_TANGENT_SPACE
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
-#endif
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
-#ifdef USE_VERT_TANGENT_SPACE
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
-#endif
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR);
-	qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
+	for (i = 0; i < ATTR_INDEX_COUNT; i++)
+		qglDisableVertexAttribArrayARB(i);
+
 	GLSL_BindNullProgram();
 
 	for ( i = 0; i < GENERICDEF_COUNT; i++)
@@ -1512,271 +1497,59 @@ void GLSL_BindNullProgram(void)
 }
 
 
-void GLSL_VertexAttribsState(uint32_t stateBits)
-{
-	uint32_t		diff;
-
-	diff = stateBits ^ glState.vertexAttribsState;
-
-	if(diff)
-	{
-		if(diff & ATTR_POSITION)
-		{
-			if(stateBits & ATTR_POSITION)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION);
-			}
-		}
-
-		if(diff & ATTR_TEXCOORD)
-		{
-			if(stateBits & ATTR_TEXCOORD)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
-			}
-		}
-
-		if(diff & ATTR_LIGHTCOORD)
-		{
-			if(stateBits & ATTR_LIGHTCOORD)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
-			}
-		}
-
-		if(diff & ATTR_NORMAL)
-		{
-			if(stateBits & ATTR_NORMAL)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
-			}
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(diff & ATTR_TANGENT)
-		{
-			if(stateBits & ATTR_TANGENT)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
-			}
-		}
-#endif
-
-		if(diff & ATTR_COLOR)
-		{
-			if(stateBits & ATTR_COLOR)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_COLOR);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR);
-			}
-		}
-
-		if(diff & ATTR_LIGHTDIRECTION)
-		{
-			if(stateBits & ATTR_LIGHTDIRECTION)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
-			}
-		}
-
-		if(diff & ATTR_POSITION2)
-		{
-			if(stateBits & ATTR_POSITION2)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
-			}
-		}
-
-		if(diff & ATTR_NORMAL2)
-		{
-			if(stateBits & ATTR_NORMAL2)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
-			}
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(diff & ATTR_TANGENT2)
-		{
-			if(stateBits & ATTR_TANGENT2)
-			{
-				GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n");
-				qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
-			}
-			else
-			{
-				GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n");
-				qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
-			}
-		}
-#endif
-	}
-
-	GLSL_VertexAttribPointers(stateBits);
-
-	glState.vertexAttribsState = stateBits;
-}
-
 void GLSL_VertexAttribPointers(uint32_t attribBits)
 {
-	qboolean animated;
 	int newFrame, oldFrame;
-	VBO_t *vbo = glState.currentVBO;
-	
-	if(!vbo)
+	vao_t *vao = glState.currentVao;
+	int attribIndex;
+	uint32_t extraOffsets[ATTR_INDEX_COUNT];
+
+	if(!vao)
 	{
-		ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VBO bound");
+		ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VAO bound");
 		return;
 	}
 
 	// don't just call LogComment, or we will get a call to va() every frame!
 	if(r_logFile->integer)
 	{
-		GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", vbo->name));
+		GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", vao->name));
 	}
 
+	for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
+		extraOffsets[attribIndex] = 0;
+
 	// position/normal/tangent are always set in case of animation
 	oldFrame = glState.vertexAttribsOldFrame;
 	newFrame = glState.vertexAttribsNewFrame;
-	animated = glState.vertexAnimation;
-	
-	if((attribBits & ATTR_POSITION) && (!(glState.vertexAttribPointersSet & ATTR_POSITION) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, vbo->stride_xyz, BUFFER_OFFSET(vbo->ofs_xyz + newFrame * vbo->size_xyz));
-		glState.vertexAttribPointersSet |= ATTR_POSITION;
-	}
-
-	if((attribBits & ATTR_TEXCOORD) && !(glState.vertexAttribPointersSet & ATTR_TEXCOORD))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD0, 2, GL_FLOAT, 0, vbo->stride_st, BUFFER_OFFSET(vbo->ofs_st));
-		glState.vertexAttribPointersSet |= ATTR_TEXCOORD;
-	}
-
-	if((attribBits & ATTR_LIGHTCOORD) && !(glState.vertexAttribPointersSet & ATTR_LIGHTCOORD))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTCOORD )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD1, 2, GL_FLOAT, 0, vbo->stride_lightmap, BUFFER_OFFSET(vbo->ofs_lightmap));
-		glState.vertexAttribPointersSet |= ATTR_LIGHTCOORD;
-	}
-
-	if((attribBits & ATTR_NORMAL) && (!(glState.vertexAttribPointersSet & ATTR_NORMAL) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_NORMAL, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_normal, BUFFER_OFFSET(vbo->ofs_normal + newFrame * vbo->size_normal));
-		glState.vertexAttribPointersSet |= ATTR_NORMAL;
-	}
-
-#ifdef USE_VERT_TANGENT_SPACE
-	if((attribBits & ATTR_TANGENT) && (!(glState.vertexAttribPointersSet & ATTR_TANGENT) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_TANGENT, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_tangent, BUFFER_OFFSET(vbo->ofs_tangent + newFrame * vbo->size_normal)); // FIXME
-		glState.vertexAttribPointersSet |= ATTR_TANGENT;
-	}
-#endif
-
-	if((attribBits & ATTR_COLOR) && !(glState.vertexAttribPointersSet & ATTR_COLOR))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_COLOR )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_COLOR, 4, GL_FLOAT, 0, vbo->stride_vertexcolor, BUFFER_OFFSET(vbo->ofs_vertexcolor));
-		glState.vertexAttribPointersSet |= ATTR_COLOR;
-	}
-
-	if((attribBits & ATTR_LIGHTDIRECTION) && !(glState.vertexAttribPointersSet & ATTR_LIGHTDIRECTION))
+	if (glState.vertexAnimation)
 	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTDIRECTION )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_LIGHTDIRECTION, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_lightdir, BUFFER_OFFSET(vbo->ofs_lightdir));
-		glState.vertexAttribPointersSet |= ATTR_LIGHTDIRECTION;
+		extraOffsets[ATTR_INDEX_POSITION]  = newFrame * vao->size_xyz;
+		extraOffsets[ATTR_INDEX_POSITION2] = oldFrame * vao->size_xyz;
+		extraOffsets[ATTR_INDEX_NORMAL]    = newFrame * vao->size_normal;
+		extraOffsets[ATTR_INDEX_NORMAL2]   = oldFrame * vao->size_normal;
+		extraOffsets[ATTR_INDEX_TANGENT]   = newFrame * vao->size_normal;
+		extraOffsets[ATTR_INDEX_TANGENT2]  = oldFrame * vao->size_normal;
 	}
 
-	if((attribBits & ATTR_POSITION2) && (!(glState.vertexAttribPointersSet & ATTR_POSITION2) || animated))
+	// this may not be bound if we're using VAOs
+	if (glRefConfig.vertexArrayObject)
 	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION2 )\n");
-
-		qglVertexAttribPointerARB(ATTR_INDEX_POSITION2, 3, GL_FLOAT, 0, vbo->stride_xyz, BUFFER_OFFSET(vbo->ofs_xyz + oldFrame * vbo->size_xyz));
-		glState.vertexAttribPointersSet |= ATTR_POSITION2;
+		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
 	}
 
-	if((attribBits & ATTR_NORMAL2) && (!(glState.vertexAttribPointersSet & ATTR_NORMAL2) || animated))
+	for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
 	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL2 )\n");
+		uint32_t attribBit = 1 << attribIndex;
+		vaoAttrib_t *vAtb;
 
-		qglVertexAttribPointerARB(ATTR_INDEX_NORMAL2, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_normal, BUFFER_OFFSET(vbo->ofs_normal + oldFrame * vbo->size_normal));
-		glState.vertexAttribPointersSet |= ATTR_NORMAL2;
-	}
+		if (!(attribBits & attribBit))
+			continue;
 
-#ifdef USE_VERT_TANGENT_SPACE
-	if((attribBits & ATTR_TANGENT2) && (!(glState.vertexAttribPointersSet & ATTR_TANGENT2) || animated))
-	{
-		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT2 )\n");
+		vAtb = &vao->attribs[attribIndex];
 
-		qglVertexAttribPointerARB(ATTR_INDEX_TANGENT2, 4, glRefConfig.packedNormalDataType, GL_TRUE, vbo->stride_tangent, BUFFER_OFFSET(vbo->ofs_tangent + oldFrame * vbo->size_normal)); // FIXME
-		glState.vertexAttribPointersSet |= ATTR_TANGENT2;
+		qglVertexAttribPointerARB(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + extraOffsets[attribIndex]));
 	}
-#endif
-
 }
 
 
diff --git a/SP/code/rend2/tr_init.c b/SP/code/rend2/tr_init.c
index 85917e7..0f1aa2d 100644
--- a/SP/code/rend2/tr_init.c
+++ b/SP/code/rend2/tr_init.c
@@ -130,6 +130,7 @@ cvar_t  *r_arb_half_float_pixel;
 cvar_t  *r_ext_framebuffer_multisample;
 cvar_t  *r_arb_seamless_cube_map;
 cvar_t  *r_arb_vertex_type_2_10_10_10_rev;
+cvar_t  *r_arb_vertex_array_object;
 
 cvar_t  *r_mergeMultidraws;
 cvar_t  *r_mergeLeafSurfaces;
@@ -1022,16 +1023,14 @@ void GL_SetDefaultState( void ) {
 	// make sure our GL state vector is set correctly
 	//
 	glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE;
+	glState.storedGlState = 0;
 
-	glState.vertexAttribsState = 0;
-	glState.vertexAttribPointersSet = 0;
 	glState.currentProgram = 0;
 	qglUseProgramObjectARB(0);
 
 	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
 	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-	glState.currentVBO = NULL;
-	glState.currentIBO = NULL;
+	glState.currentVao = NULL;
 
 	qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
 	qglDepthMask( GL_TRUE );
@@ -1070,6 +1069,11 @@ void GL_SetDefaultState( void ) {
 	}
 
 //----(SA)	end
+
+	// GL_POLYGON_OFFSET_FILL will be glEnable()d when this is used
+	qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
+
+	qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );	// FIXME: get color of sky
 }
 
 /*
@@ -1267,6 +1271,7 @@ void R_Register( void ) {
 	r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH);
 	r_arb_seamless_cube_map = ri.Cvar_Get( "r_arb_seamless_cube_map", "0", CVAR_ARCHIVE | CVAR_LATCH);
 	r_arb_vertex_type_2_10_10_10_rev = ri.Cvar_Get( "r_arb_vertex_type_2_10_10_10_rev", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_arb_vertex_array_object = ri.Cvar_Get( "r_arb_vertex_array_object", "1", CVAR_ARCHIVE | CVAR_LATCH);
 
 	r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic",
 			"0", CVAR_ARCHIVE | CVAR_LATCH );
@@ -1609,7 +1614,7 @@ void R_Init( void ) {
 
 	GLSL_InitGPUShaders();
 
-	R_InitVBOs();
+	R_InitVaos();
 
 	R_InitShaders();
 
@@ -1663,7 +1668,7 @@ void RE_Shutdown( qboolean destroyWindow ) {
 		if (glRefConfig.framebufferObject)
 			FBO_Shutdown();
 		R_DeleteTextures();
-		R_ShutdownVBOs();
+		R_ShutdownVaos();
 		GLSL_ShutdownGPUShaders();
 	}
 
diff --git a/SP/code/rend2/tr_light.c b/SP/code/rend2/tr_light.c
index c8803b4..e622dff 100644
--- a/SP/code/rend2/tr_light.c
+++ b/SP/code/rend2/tr_light.c
@@ -114,7 +114,7 @@ void R_DlightBmodel( bmodel_t *bmodel ) {
 			case SF_FACE:
 			case SF_GRID:
 			case SF_TRIANGLES:
-			case SF_VBO_MESH:
+			case SF_VAO_MESH:
 				((srfBspSurface_t *)surf->data)->dlightBits = mask;
 				break;
 
diff --git a/SP/code/rend2/tr_local.h b/SP/code/rend2/tr_local.h
index 8727f64..ccdd62c 100644
--- a/SP/code/rend2/tr_local.h
+++ b/SP/code/rend2/tr_local.h
@@ -56,8 +56,7 @@ typedef unsigned int glIndex_t;
 
 #define	MAX_FBOS      64
 #define MAX_VISCOUNTS 5
-#define MAX_VBOS      4096
-#define MAX_IBOS      4096
+#define MAX_VAOS      4096
 
 #define MAX_CALC_PSHADOWS    64
 #define MAX_DRAWN_PSHADOWS    16 // do not increase past 32, because bit flags are used on surfaces
@@ -67,6 +66,7 @@ typedef unsigned int glIndex_t;
  
 #define USE_VERT_TANGENT_SPACE
 
+
 // a trRefEntity_t has all the information passed in by
 // the client game, as well as some locally derived info
 typedef struct {
@@ -135,50 +135,42 @@ typedef struct image_s {
 	struct image_s* next;
 } image_t;
 
+// Ensure this is >= the ATTR_INDEX_COUNT enum below
+#define VAO_MAX_ATTRIBS 16
+
 typedef enum
 {
-	VBO_USAGE_STATIC,
-	VBO_USAGE_DYNAMIC
-} vboUsage_t;
+	VAO_USAGE_STATIC,
+	VAO_USAGE_DYNAMIC
+} vaoUsage_t;
+
+typedef struct vaoAttrib_s
+{
+	uint32_t enabled;
+	uint32_t count;
+	uint32_t type;
+	uint32_t normalized;
+	uint32_t stride;
+	uint32_t offset;
+}
+vaoAttrib_t;
 
-typedef struct VBO_s
+typedef struct vao_s
 {
 	char            name[MAX_QPATH];
 
+	uint32_t        vao;
+
 	uint32_t        vertexesVBO;
 	int             vertexesSize;	// amount of memory data allocated for all vertices in bytes
-	uint32_t        ofs_xyz;
-	uint32_t        ofs_normal;
-	uint32_t        ofs_st;
-	uint32_t        ofs_lightmap;
-	uint32_t        ofs_vertexcolor;
-	uint32_t        ofs_lightdir;
-#ifdef USE_VERT_TANGENT_SPACE
-	uint32_t        ofs_tangent;
-#endif
-	uint32_t        stride_xyz;
-	uint32_t        stride_normal;
-	uint32_t        stride_st;
-	uint32_t        stride_lightmap;
-	uint32_t        stride_vertexcolor;
-	uint32_t        stride_lightdir;
-#ifdef USE_VERT_TANGENT_SPACE
-	uint32_t        stride_tangent;
-#endif
+	vaoAttrib_t     attribs[VAO_MAX_ATTRIBS];
+
 	uint32_t        size_xyz;
 	uint32_t        size_normal;
 
-	int             attribs;
-} VBO_t;
-
-typedef struct IBO_s
-{
-	char            name[MAX_QPATH];
-
-	uint32_t        indexesVBO;
+	uint32_t        indexesIBO;
 	int             indexesSize;	// amount of memory data allocated for all triangles in bytes
-//  uint32_t        ofsIndexes;
-} IBO_t;
+} vao_t;
 
 //===============================================================================
 
@@ -602,8 +594,8 @@ typedef struct dlight_s {
 enum
 {
 	ATTR_INDEX_POSITION       = 0,
-	ATTR_INDEX_TEXCOORD0      = 1,
-	ATTR_INDEX_TEXCOORD1      = 2,
+	ATTR_INDEX_TEXCOORD       = 1,
+	ATTR_INDEX_LIGHTCOORD     = 2,
 	ATTR_INDEX_TANGENT        = 3,
 	ATTR_INDEX_NORMAL         = 4,
 	ATTR_INDEX_COLOR          = 5,
@@ -615,26 +607,28 @@ enum
 	// GPU vertex animations
 	ATTR_INDEX_POSITION2      = 10,
 	ATTR_INDEX_TANGENT2       = 11,
-	ATTR_INDEX_NORMAL2        = 12
+	ATTR_INDEX_NORMAL2        = 12,
+	
+	ATTR_INDEX_COUNT          = 13
 };
 
 enum
 {
-	ATTR_POSITION =       0x0001,
-	ATTR_TEXCOORD =       0x0002,
-	ATTR_LIGHTCOORD =     0x0004,
-	ATTR_TANGENT =        0x0008,
-	ATTR_NORMAL =         0x0010,
-	ATTR_COLOR =          0x0020,
-	ATTR_PAINTCOLOR =     0x0040,
-	ATTR_LIGHTDIRECTION = 0x0080,
-	ATTR_BONE_INDEXES =   0x0100,
-	ATTR_BONE_WEIGHTS =   0x0200,
+	ATTR_POSITION =       1 << ATTR_INDEX_POSITION,
+	ATTR_TEXCOORD =       1 << ATTR_INDEX_TEXCOORD,
+	ATTR_LIGHTCOORD =     1 << ATTR_INDEX_LIGHTCOORD,
+	ATTR_TANGENT =        1 << ATTR_INDEX_TANGENT,
+	ATTR_NORMAL =         1 << ATTR_INDEX_NORMAL,
+	ATTR_COLOR =          1 << ATTR_INDEX_COLOR,
+	ATTR_PAINTCOLOR =     1 << ATTR_INDEX_PAINTCOLOR,
+	ATTR_LIGHTDIRECTION = 1 << ATTR_INDEX_LIGHTDIRECTION,
+	ATTR_BONE_INDEXES =   1 << ATTR_INDEX_BONE_INDEXES,
+	ATTR_BONE_WEIGHTS =   1 << ATTR_INDEX_BONE_WEIGHTS,
 
 	// for .md3 interpolation
-	ATTR_POSITION2 =      0x0400,
-	ATTR_TANGENT2 =       0x0800,
-	ATTR_NORMAL2 =        0x1000,
+	ATTR_POSITION2 =      1 << ATTR_INDEX_POSITION2,
+	ATTR_TANGENT2 =       1 << ATTR_INDEX_TANGENT2,
+	ATTR_NORMAL2 =        1 << ATTR_INDEX_NORMAL2,
 
 	ATTR_DEFAULT = ATTR_POSITION,
 	ATTR_BITS =	ATTR_POSITION |
@@ -976,8 +970,8 @@ typedef enum {
 	SF_FLARE,
 	SF_ENTITY,              // beams, rails, lightning, etc that can be determined by entity
 	SF_DISPLAY_LIST,
-	SF_VBO_MESH,
-	SF_VBO_MDVMESH,
+	SF_VAO_MESH,
+	SF_VAO_MDVMESH,
 
 	SF_NUM_SURFACE_TYPES,
 	SF_MAX = 0xffffffff         // ensures that sizeof( surfaceType_t ) == sizeof( int )
@@ -1040,7 +1034,7 @@ typedef struct
 #define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0},  {0, 0, 0, 0}}
 #endif
 
-// srfBspSurface_t covers SF_GRID, SF_TRIANGLES, SF_POLY, and SF_VBO_MESH
+// srfBspSurface_t covers SF_GRID, SF_TRIANGLES, SF_POLY, and SF_VAO_MESH
 typedef struct srfBspSurface_s
 {
 	surfaceType_t surfaceType;
@@ -1070,8 +1064,7 @@ typedef struct srfBspSurface_s
 	glIndex_t       maxIndex;
 
 	// static render data
-	VBO_t          *vbo;
-	IBO_t          *ibo;
+	vao_t          *vao;
 
 	// SF_GRID specific variables after here
 
@@ -1133,7 +1126,7 @@ typedef struct srfIQModel_s {
 	int		first_triangle, num_triangles;
 } srfIQModel_t;
 
-typedef struct srfVBOMDVMesh_s
+typedef struct srfVaoMdvMesh_s
 {
 	surfaceType_t   surfaceType;
 
@@ -1147,9 +1140,8 @@ typedef struct srfVBOMDVMesh_s
 	glIndex_t       maxIndex;
 
 	// static render data
-	VBO_t          *vbo;
-	IBO_t          *ibo;
-} srfVBOMDVMesh_t;
+	vao_t          *vao;
+} srfVaoMdvMesh_t;
 
 extern void( *rb_surfaceTable[SF_NUM_SURFACE_TYPES] ) ( void * );
 
@@ -1371,8 +1363,8 @@ typedef struct mdvModel_s
 	int             numSurfaces;
 	mdvSurface_t   *surfaces;
 
-	int             numVBOSurfaces;
-	srfVBOMDVMesh_t  *vboSurfaces;
+	int             numVaoSurfaces;
+	srfVaoMdvMesh_t  *vaoSurfaces;
 
 	int             numSkins;
 } mdvModel_t;
@@ -1512,17 +1504,16 @@ typedef struct {
 	qboolean finishCalled;
 	int texEnv[2];
 	int faceCulling;
-	unsigned long glStateBits;
-	uint32_t		vertexAttribsState;
-	uint32_t		vertexAttribPointersSet;
+	int         faceCullFront;
+	uint32_t    glStateBits;
+	uint32_t    storedGlState;
 	uint32_t        vertexAttribsNewFrame;
 	uint32_t        vertexAttribsOldFrame;
 	float           vertexAttribsInterpolation;
 	qboolean        vertexAnimation;
 	shaderProgram_t *currentProgram;
 	FBO_t          *currentFBO;
-	VBO_t          *currentVBO;
-	IBO_t          *currentIBO;
+	vao_t          *currentVao;
 	mat4_t        modelview;
 	mat4_t        projection;
 	mat4_t		modelviewProjection;
@@ -1571,6 +1562,7 @@ typedef struct {
 	GLenum packedNormalDataType;
 
 	qboolean floatLightmap;
+	qboolean vertexArrayObject;
 } glRefConfig_t;
 
 typedef struct {
@@ -1578,13 +1570,12 @@ typedef struct {
 	int     c_surfBatches;
 	float c_overDraw;
 
-	int		c_vboVertexBuffers;
-	int		c_vboIndexBuffers;
-	int		c_vboVertexes;
-	int		c_vboIndexes;
+	int		c_vaoBinds;
+	int		c_vaoVertexes;
+	int		c_vaoIndexes;
 
-	int     c_staticVboDraws;
-	int     c_dynamicVboDraws;
+	int     c_staticVaoDraws;
+	int     c_dynamicVaoDraws;
 
 	int     c_multidraws;
 	int     c_multidrawsMerged;
@@ -1797,14 +1788,11 @@ typedef struct {
 	int						numFBOs;
 	FBO_t					*fbos[MAX_FBOS];
 
-	int						numVBOs;
-	VBO_t					*vbos[MAX_VBOS];
-
-	int						numIBOs;
-	IBO_t					*ibos[MAX_IBOS];
+	int						numVaos;
+	vao_t					*vaos[MAX_VAOS];
 
 	// Ridah
-	int numCacheImages;
+	//int numCacheImages;
 
 	// shader indexes from other modules will be looked up in tr.shaders[]
 	// shader indexes from drawsurfs will be looked up in sortedShaders[]
@@ -1934,6 +1922,7 @@ extern  cvar_t  *r_arb_half_float_pixel;
 extern  cvar_t  *r_ext_framebuffer_multisample;
 extern  cvar_t  *r_arb_seamless_cube_map;
 extern  cvar_t  *r_arb_vertex_type_2_10_10_10_rev;
+extern  cvar_t  *r_arb_vertex_array_object;
 
 extern cvar_t  *r_waterFogColor;        //----(SA)	added
 extern cvar_t  *r_mapFogColor;          //----(SA)	added
@@ -2297,9 +2286,9 @@ typedef struct shaderCommands_s
 	uint32_t    lightdir[SHADER_MAX_VERTEXES] QALIGN(16);
 	//int			vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16);
 
-	VBO_t       *vbo;
-	IBO_t       *ibo;
-	qboolean    useInternalVBO;
+	void *attribPointers[ATTR_INDEX_COUNT];
+	vao_t       *vao;
+	qboolean    useInternalVao;
 
 	stageVars_t	svars QALIGN(16);
 
@@ -2342,7 +2331,7 @@ void RB_EndSurface( void );
 void RB_CheckOverflow( int verts, int indexes );
 #define RB_CHECKOVERFLOW( v,i ) if ( tess.numVertexes + ( v ) >= SHADER_MAX_VERTEXES || tess.numIndexes + ( i ) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow( v,i );}
 
-void R_DrawElementsVBO( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex );
+void R_DrawElementsVao( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex );
 void RB_StageIteratorGeneric( void );
 void RB_StageIteratorSky( void );
 void RB_StageIteratorVertexLitTexture( void );
@@ -2460,28 +2449,24 @@ VERTEX BUFFER OBJECTS
 ============================================================
 */
 
-uint32_t R_VboPackTangent(vec4_t v);
-uint32_t R_VboPackNormal(vec3_t v);
-void R_VboUnpackTangent(vec4_t v, uint32_t b);
-void R_VboUnpackNormal(vec3_t v, uint32_t b);
-
-VBO_t          *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage);
-VBO_t          *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * vertexes, uint32_t stateBits, vboUsage_t usage);
+uint32_t R_VaoPackTangent(vec4_t v);
+uint32_t R_VaoPackNormal(vec3_t v);
+void R_VaoUnpackTangent(vec4_t v, uint32_t b);
+void R_VaoUnpackNormal(vec3_t v, uint32_t b);
 
-IBO_t          *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage);
-IBO_t          *R_CreateIBO2(const char *name, int numIndexes, glIndex_t * inIndexes, vboUsage_t usage);
+vao_t          *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage);
+vao_t          *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *inIndexes);
 
-void            R_BindVBO(VBO_t * vbo);
-void            R_BindNullVBO(void);
+void            R_BindVao(vao_t *vao);
+void            R_BindNullVao(void);
 
-void            R_BindIBO(IBO_t * ibo);
-void            R_BindNullIBO(void);
+void Vao_SetVertexPointers(vao_t *vao);
 
-void            R_InitVBOs(void);
-void            R_ShutdownVBOs(void);
-void            R_VBOList_f(void);
+void            R_InitVaos(void);
+void            R_ShutdownVaos(void);
+void            R_VaoList_f(void);
 
-void            RB_UpdateVBOs(unsigned int attribBits);
+void            RB_UpdateTessVao(unsigned int attribBits);
 
 
 /*
@@ -2494,7 +2479,6 @@ GLSL
 
 void GLSL_InitGPUShaders(void);
 void GLSL_ShutdownGPUShaders(void);
-void GLSL_VertexAttribsState(uint32_t stateBits);
 void GLSL_VertexAttribPointers(uint32_t attribBits);
 void GLSL_BindProgram(shaderProgram_t * program);
 void GLSL_BindNullProgram(void);
diff --git a/SP/code/rend2/tr_main.c b/SP/code/rend2/tr_main.c
index fcc9db9..200a6a9 100644
--- a/SP/code/rend2/tr_main.c
+++ b/SP/code/rend2/tr_main.c
@@ -1874,7 +1874,7 @@ static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128
 			shortest = len;
 		}
 
-		R_VboUnpackNormal(tNormal, tess.normal[tess.indexes[i]]);
+		R_VaoUnpackNormal(tNormal, tess.normal[tess.indexes[i]]);
 
 		if ( DotProduct( normal, tNormal ) >= 0 )
  		{
diff --git a/SP/code/rend2/tr_mesh.c b/SP/code/rend2/tr_mesh.c
index 0214b93..20b66f8 100644
--- a/SP/code/rend2/tr_mesh.c
+++ b/SP/code/rend2/tr_mesh.c
@@ -434,8 +434,8 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
 		// don't add third_person objects if not viewing through a portal
 		if ( !personalModel ) {
 // GR - tessellate according to model capabilities
-			srfVBOMDVMesh_t *vboSurface = &model->vboSurfaces[i];
-			R_AddDrawSurf( (void *)vboSurface, shader, fogNum, qfalse, qfalse, cubemapIndex, tr.currentModel->ATI_tess );
+			srfVaoMdvMesh_t *vaoSurface = &model->vaoSurfaces[i];
+			R_AddDrawSurf( (void *)vaoSurface, shader, fogNum, qfalse, qfalse, cubemapIndex, tr.currentModel->ATI_tess );
 		}
 
 		surface++;
diff --git a/SP/code/rend2/tr_model.c b/SP/code/rend2/tr_model.c
index 59cad61..4031643 100644
--- a/SP/code/rend2/tr_model.c
+++ b/SP/code/rend2/tr_model.c
@@ -925,14 +925,14 @@ static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *modN
 	}
 
 	{
-		srfVBOMDVMesh_t *vboSurf;
+		srfVaoMdvMesh_t *vaoSurf;
 
-		mdvModel->numVBOSurfaces = mdvModel->numSurfaces;
-		mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low);
+		mdvModel->numVaoSurfaces = mdvModel->numSurfaces;
+		mdvModel->vaoSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vaoSurfaces) * mdvModel->numSurfaces, h_low);
 
-		vboSurf = mdvModel->vboSurfaces;
+		vaoSurf = mdvModel->vaoSurfaces;
 		surf = mdvModel->surfaces;
-		for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++)
+		for (i = 0; i < mdvModel->numSurfaces; i++, vaoSurf++, surf++)
 		{
 			vec3_t *verts;
 			vec2_t *texcoords;
@@ -982,13 +982,13 @@ static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *modN
 
 				VectorCopy(v->xyz,       verts[j]);
 
-				normals[j] = R_VboPackNormal(v->normal);
+				normals[j] = R_VaoPackNormal(v->normal);
 #ifdef USE_VERT_TANGENT_SPACE
 				CrossProduct(v->normal, v->tangent, nxt);
 				VectorCopy(v->tangent, tangent);
 				tangent[3] = (DotProduct(nxt, v->bitangent) < 0.0f) ? -1.0f : 1.0f;
 
-				tangents[j] = R_VboPackTangent(tangent);
+				tangents[j] = R_VaoPackTangent(tangent);
 #endif
 			}
 
@@ -998,37 +998,76 @@ static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *modN
 				texcoords[j][1] = st->st[1];
 			}
 
-			vboSurf->surfaceType = SF_VBO_MDVMESH;
-			vboSurf->mdvModel = mdvModel;
-			vboSurf->mdvSurface = surf;
-			vboSurf->numIndexes = surf->numIndexes;
-			vboSurf->numVerts = surf->numVerts;
-			
-			vboSurf->minIndex = 0;
-			vboSurf->maxIndex = surf->numVerts;
+			vaoSurf->surfaceType = SF_VAO_MDVMESH;
+			vaoSurf->mdvModel = mdvModel;
+			vaoSurf->mdvSurface = surf;
+			vaoSurf->numIndexes = surf->numIndexes;
+			vaoSurf->numVerts = surf->numVerts;
+
+			vaoSurf->minIndex = 0;
+			vaoSurf->maxIndex = surf->numVerts;
 
-			vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC);
+			vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC);
 
-			vboSurf->vbo->ofs_xyz       = ofs_xyz;
-			vboSurf->vbo->ofs_normal    = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].enabled = 1;
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->ofs_tangent   = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].enabled = 1;
 #endif
-			vboSurf->vbo->ofs_st        = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].enabled = 1;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].count = 2;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].type = GL_FLOAT;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].normalized = GL_FALSE;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].stride = sizeof(*verts);
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].stride = sizeof(*verts);
+
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].stride = sizeof(*normals);
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].stride = sizeof(*normals);
 
-			vboSurf->vbo->stride_xyz       = sizeof(*verts);
-			vboSurf->vbo->stride_normal    = sizeof(*normals);
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->stride_tangent   = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].stride = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].stride = sizeof(*tangents);
 #endif
-			vboSurf->vbo->stride_st        = sizeof(*st);
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].offset = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].stride = sizeof(*st);
 
-			vboSurf->vbo->size_xyz    = sizeof(*verts) * surf->numVerts;
-			vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts;
+			vaoSurf->vao->size_xyz    = sizeof(*verts)   * surf->numVerts;
+			vaoSurf->vao->size_normal = sizeof(*normals) * surf->numVerts;
 
-			ri.Free(data);
+			Vao_SetVertexPointers(vaoSurf->vao);
 
-			vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numIndexes, surf->indexes, VBO_USAGE_STATIC);
+			ri.Free(data);
 		}
 	}
 
@@ -1357,14 +1396,14 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, const char *modN
 
 
 	{
-		srfVBOMDVMesh_t *vboSurf;
+		srfVaoMdvMesh_t *vaoSurf;
 
-		mdvModel->numVBOSurfaces = mdvModel->numSurfaces;
-		mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low);
+		mdvModel->numVaoSurfaces = mdvModel->numSurfaces;
+		mdvModel->vaoSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vaoSurfaces) * mdvModel->numSurfaces, h_low);
 
-		vboSurf = mdvModel->vboSurfaces;
+		vaoSurf = mdvModel->vaoSurfaces;
 		surf = mdvModel->surfaces;
-		for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++)
+		for (i = 0; i < mdvModel->numSurfaces; i++, vaoSurf++, surf++)
 		{
 			vec3_t *verts;
 			vec2_t *texcoords;
@@ -1414,13 +1453,13 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, const char *modN
 
 				VectorCopy(v->xyz,       verts[j]);
 
-				normals[j] = R_VboPackNormal(v->normal);
+				normals[j] = R_VaoPackNormal(v->normal);
 #ifdef USE_VERT_TANGENT_SPACE
 				CrossProduct(v->normal, v->tangent, nxt);
 				VectorCopy(v->tangent, tangent);
 				tangent[3] = (DotProduct(nxt, v->bitangent) < 0.0f) ? -1.0f : 1.0f;
 
-				tangents[j] = R_VboPackTangent(tangent);
+				tangents[j] = R_VaoPackTangent(tangent);
 #endif
 			}
 
@@ -1430,37 +1469,76 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, const char *modN
 				texcoords[j][1] = st->st[1];
 			}
 
-			vboSurf->surfaceType = SF_VBO_MDVMESH;
-			vboSurf->mdvModel = mdvModel;
-			vboSurf->mdvSurface = surf;
-			vboSurf->numIndexes = surf->numIndexes;
-			vboSurf->numVerts = surf->numVerts;
-			
-			vboSurf->minIndex = 0;
-			vboSurf->maxIndex = surf->numVerts;
+			vaoSurf->surfaceType = SF_VAO_MDVMESH;
+			vaoSurf->mdvModel = mdvModel;
+			vaoSurf->mdvSurface = surf;
+			vaoSurf->numIndexes = surf->numIndexes;
+			vaoSurf->numVerts = surf->numVerts;
+
+			vaoSurf->minIndex = 0;
+			vaoSurf->maxIndex = surf->numVerts;
 
-			vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC);
+			vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC);
 
-			vboSurf->vbo->ofs_xyz       = ofs_xyz;
-			vboSurf->vbo->ofs_normal    = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].enabled = 1;
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->ofs_tangent   = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].enabled = 1;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].enabled = 1;
 #endif
-			vboSurf->vbo->ofs_st        = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].enabled = 1;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].count = 3;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].count = 4;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].count = 2;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].type = GL_FLOAT;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].type = glRefConfig.packedNormalDataType;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].type = GL_FLOAT;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].normalized = GL_FALSE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].normalized = GL_TRUE;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].normalized = GL_FALSE;
+
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION  ].stride = sizeof(*verts);
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].offset = ofs_xyz;
+			vaoSurf->vao->attribs[ATTR_INDEX_POSITION2 ].stride = sizeof(*verts);
+
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL    ].stride = sizeof(*normals);
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].offset = ofs_normal;
+			vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2   ].stride = sizeof(*normals);
 
-			vboSurf->vbo->stride_xyz       = sizeof(*verts);
-			vboSurf->vbo->stride_normal    = sizeof(*normals);
 #ifdef USE_VERT_TANGENT_SPACE
-			vboSurf->vbo->stride_tangent   = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT   ].stride = sizeof(*tangents);
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].offset = ofs_tangent;
+			vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2  ].stride = sizeof(*tangents);
 #endif
-			vboSurf->vbo->stride_st        = sizeof(*st);
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].offset = ofs_st;
+			vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD  ].stride = sizeof(*st);
 
-			vboSurf->vbo->size_xyz    = sizeof(*verts) * surf->numVerts;
-			vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts;
+			vaoSurf->vao->size_xyz    = sizeof(*verts)   * surf->numVerts;
+			vaoSurf->vao->size_normal = sizeof(*normals) * surf->numVerts;
 
-			ri.Free(data);
+			Vao_SetVertexPointers(vaoSurf->vao);
 
-			vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numIndexes, surf->indexes, VBO_USAGE_STATIC);
+			ri.Free(data);
 		}
 	}
 
diff --git a/SP/code/rend2/tr_model_iqm.c b/SP/code/rend2/tr_model_iqm.c
index 86656fa..0079506 100644
--- a/SP/code/rend2/tr_model_iqm.c
+++ b/SP/code/rend2/tr_model_iqm.c
@@ -1130,7 +1130,7 @@ void RB_IQMSurfaceAnim( surfaceType_t *surface ) {
 			normal[1] = DotProduct(&nrmMat[3], &data->normals[3*vtx]);
 			normal[2] = DotProduct(&nrmMat[6], &data->normals[3*vtx]);
 
-			*outNormal = R_VboPackNormal(normal);
+			*outNormal = R_VaoPackNormal(normal);
 
 #ifdef USE_VERT_TANGENT_SPACE
 			tangent[0] = DotProduct(&nrmMat[0], &data->tangents[4*vtx]);
@@ -1138,7 +1138,7 @@ void RB_IQMSurfaceAnim( surfaceType_t *surface ) {
 			tangent[2] = DotProduct(&nrmMat[6], &data->tangents[4*vtx]);
 			tangent[3] = data->tangents[4*vtx+3];
 
-			*outTangent++ = R_VboPackTangent(tangent);
+			*outTangent++ = R_VaoPackTangent(tangent);
 #endif
 		}
 
diff --git a/SP/code/rend2/tr_shade.c b/SP/code/rend2/tr_shade.c
index b1f8ed3..15fd2a7 100644
--- a/SP/code/rend2/tr_shade.c
+++ b/SP/code/rend2/tr_shade.c
@@ -40,7 +40,7 @@ If you have questions concerning this license or the applicable additional terms
 */
 
 
-void R_DrawElementsVBO( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex )
+void R_DrawElementsVao( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex )
 {
 	if (glRefConfig.drawRangeElements)
 		qglDrawRangeElementsEXT(GL_TRIANGLES, minIndex, maxIndex, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
@@ -50,7 +50,7 @@ void R_DrawElementsVBO( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex
 }
 
 
-static void R_DrawMultiElementsVBO( int multiDrawPrimitives, glIndex_t *multiDrawMinIndex, glIndex_t *multiDrawMaxIndex, 
+static void R_DrawMultiElementsVao( int multiDrawPrimitives, glIndex_t *multiDrawMinIndex, glIndex_t *multiDrawMaxIndex, 
 	GLsizei *multiDrawNumIndexes, glIndex_t **multiDrawFirstIndex)
 {
 	if (glRefConfig.multiDrawArrays)
@@ -152,7 +152,6 @@ static void DrawTris (shaderCommands_t *input) {
 		shaderProgram_t *sp = &tr.textureColorShader;
 		vec4_t color;
 
-		GLSL_VertexAttribsState(ATTR_POSITION);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -161,11 +160,11 @@ static void DrawTris (shaderCommands_t *input) {
 
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 	}
 
@@ -210,7 +209,7 @@ void RB_BeginSurface( shader_t *shader, int fogNum, int cubemapIndex ) {
 	tess.xstages = state->stages;
 	tess.numPasses = state->numUnfoggedPasses;
 	tess.currentStageIteratorFunc = state->optimalStageIteratorFunc;
-	tess.useInternalVBO = qtrue;
+	tess.useInternalVao = qtrue;
 
 	tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
 	if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) {
@@ -441,11 +440,11 @@ static void ProjectDlightTexture( void ) {
 		if (tess.multiDrawPrimitives)
 		{
 			shaderCommands_t *input = &tess;
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+			R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 		}
 
 		backEnd.pc.c_totalIndexes += tess.numIndexes;
@@ -902,11 +901,11 @@ static void ForwardDlight( void ) {
 
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 
 		backEnd.pc.c_totalIndexes += tess.numIndexes;
@@ -978,11 +977,11 @@ static void ProjectPshadowVBOGLSL( void ) {
 
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 
 		backEnd.pc.c_totalIndexes += tess.numIndexes;
@@ -1115,11 +1114,11 @@ static void RB_FogPass( int wolfFog ) {
 	if (tess.multiDrawPrimitives)
 	{
 		shaderCommands_t *input = &tess;
-		R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+		R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 	}
 	else
 	{
-		R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+		R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 	}
 }
 
@@ -1521,11 +1520,11 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input )
 		//
 		if (input->multiDrawPrimitives)
 		{
-			R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+			R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 		}
 		else
 		{
-			R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+			R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 		}
 
 		// allow skipping out to show just lightmaps during development
@@ -1585,11 +1584,11 @@ static void RB_RenderShadowmap( shaderCommands_t *input )
 
 			if (input->multiDrawPrimitives)
 			{
-				R_DrawMultiElementsVBO(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
+				R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex);
 			}
 			else
 			{
-				R_DrawElementsVBO(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
+				R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex);
 			}
 		}
 	}
@@ -1612,20 +1611,20 @@ void RB_StageIteratorGeneric( void )
 		return;
 	}
 
-	if (tess.useInternalVBO)
+	if (tess.useInternalVao)
 	{
 		RB_DeformTessGeometry();
 	}
 
 	vertexAttribs = RB_CalcShaderVertexAttribs( input );
 
-	if (tess.useInternalVBO)
+	if (tess.useInternalVao)
 	{
-		RB_UpdateVBOs(vertexAttribs);
+		RB_UpdateTessVao(vertexAttribs);
 	}
 	else
 	{
-		backEnd.pc.c_staticVboDraws++;
+		backEnd.pc.c_staticVaoDraws++;
 	}
 
 	//
@@ -1666,13 +1665,13 @@ void RB_StageIteratorGeneric( void )
 	if ( input->shader->polygonOffset )
 	{
 		qglEnable( GL_POLYGON_OFFSET_FILL );
-		qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
 	}
 
 	//
 	// Set vertex attribs and pointers
 	//
-	GLSL_VertexAttribsState(vertexAttribs);
+	if (glState.vertexAnimation)
+		GLSL_VertexAttribPointers(vertexAttribs & (ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TANGENT | ATTR_TANGENT2));
 
 	//
 	// render depth if in depthfill mode
diff --git a/SP/code/rend2/tr_shade_calc.c b/SP/code/rend2/tr_shade_calc.c
index 11859a2..4bfad4c 100644
--- a/SP/code/rend2/tr_shade_calc.c
+++ b/SP/code/rend2/tr_shade_calc.c
@@ -153,7 +153,7 @@ void RB_CalcDeformVertexes( deformStage_t *ds ) {
 			float dot;
 			vec3_t fNormal;
 
-			R_VboUnpackNormal(fNormal, *normal);
+			R_VaoUnpackNormal(fNormal, *normal);
 
 			scale = WAVEVALUE( table, ds->deformationWave.base,
 							   ds->deformationWave.amplitude,
@@ -181,7 +181,7 @@ void RB_CalcDeformVertexes( deformStage_t *ds ) {
 
 		for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal++ )
 		{
-			R_VboUnpackNormal(offset, *normal);
+			R_VaoUnpackNormal(offset, *normal);
 
 			xyz[0] += offset[0] * scale;
 			xyz[1] += offset[1] * scale;
@@ -200,7 +200,7 @@ void RB_CalcDeformVertexes( deformStage_t *ds ) {
 							   ds->deformationWave.phase + off,
 							   ds->deformationWave.frequency );
 
-			R_VboUnpackNormal(offset, *normal);
+			R_VaoUnpackNormal(offset, *normal);
 
 			xyz[0] += offset[0] * scale;
 			xyz[1] += offset[1] * scale;
@@ -225,7 +225,7 @@ void RB_CalcDeformNormals( deformStage_t *ds ) {
 	for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal++ ) {
 		vec3_t fNormal;
 
-		R_VboUnpackNormal(fNormal, *normal);
+		R_VaoUnpackNormal(fNormal, *normal);
 
 		scale = 0.98f;
 		scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
@@ -244,7 +244,7 @@ void RB_CalcDeformNormals( deformStage_t *ds ) {
 
 		VectorNormalizeFast( fNormal );
 
-		*normal = R_VboPackNormal(fNormal);
+		*normal = R_VaoPackNormal(fNormal);
 	}
 }
 
@@ -268,7 +268,7 @@ void RB_CalcBulgeVertexes( deformStage_t *ds ) {
 		float scale;
 		vec3_t fNormal;
 
-		R_VboUnpackNormal(fNormal, *normal);
+		R_VaoUnpackNormal(fNormal, *normal);
 
 		off = (float)( FUNCTABLE_SIZE / ( M_PI * 2 ) ) * ( st[0] * ds->bulgeWidth + now );
 
@@ -332,7 +332,7 @@ void DeformText( const char *text ) {
 	height[1] = 0;
 	height[2] = -1;
 
-	R_VboUnpackNormal(fNormal, tess.normal[0]);
+	R_VaoUnpackNormal(fNormal, tess.normal[0]);
 	CrossProduct( fNormal, height, width );
 
 	// find the midpoint of the box
@@ -822,7 +822,7 @@ void RB_CalcFireRiseEnvTexCoords( float *st ) {
 	{
 		VectorNormalizeFast( viewer );
 
-		R_VboUnpackNormal(fNormal, *normal);
+		R_VaoUnpackNormal(fNormal, *normal);
 
 		d = DotProduct( fNormal, viewer );
 
diff --git a/SP/code/rend2/tr_sky.c b/SP/code/rend2/tr_sky.c
index 852ffff..e029f8c 100644
--- a/SP/code/rend2/tr_sky.c
+++ b/SP/code/rend2/tr_sky.c
@@ -432,12 +432,11 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
 	tess.maxIndex = tess.numVertexes;
 
 	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
-	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
 /*
 	{
 		shaderProgram_t *sp = &tr.textureColorShader;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -453,7 +452,6 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
 		shaderProgram_t *sp = &tr.lightallShader[0];
 		vec4_t vector;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -477,7 +475,7 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
 		GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector);
 	}
 
-	R_DrawElementsVBO(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	//qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t)));
 	
@@ -551,12 +549,11 @@ static void DrawSkySideInner( struct image_s *image, const int mins[2], const in
 	tess.maxIndex = tess.numVertexes;
 
 	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
-	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
 /*
 	{
 		shaderProgram_t *sp = &tr.textureColorShader;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -572,7 +569,6 @@ static void DrawSkySideInner( struct image_s *image, const int mins[2], const in
 		shaderProgram_t *sp = &tr.lightallShader[0];
 		vec4_t vector;
 
-		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
 		GLSL_BindProgram(sp);
 		
 		GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
@@ -596,7 +592,7 @@ static void DrawSkySideInner( struct image_s *image, const int mins[2], const in
 		GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector);
 	}
 
-	R_DrawElementsVBO(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	//qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t)));
 	
diff --git a/SP/code/rend2/tr_surface.c b/SP/code/rend2/tr_surface.c
index 988073e..79253e5 100644
--- a/SP/code/rend2/tr_surface.c
+++ b/SP/code/rend2/tr_surface.c
@@ -73,19 +73,18 @@ void RB_CheckOverflow( int verts, int indexes ) {
 	RB_BeginSurface( tess.shader, tess.fogNum, tess.cubemapIndex );
 }
 
-void RB_CheckVBOandIBO(VBO_t *vbo, IBO_t *ibo)
+void RB_CheckVao(vao_t *vao)
 {
-	if (!(vbo == glState.currentVBO && ibo == glState.currentIBO) || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES)
+	if (vao != glState.currentVao || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES)
 	{
 		RB_EndSurface();
 		RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex);
 
-		R_BindVBO(vbo);
-		R_BindIBO(ibo);
+		R_BindVao(vao);
 	}
 
-	if (vbo != tess.vbo && ibo != tess.ibo)
-		tess.useInternalVBO = qfalse;
+	if (vao != tess.vao)
+		tess.useInternalVao = qfalse;
 }
 
 /*
@@ -133,7 +132,7 @@ void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4],
 	tess.normal[ndx] =
 	tess.normal[ndx+1] =
 	tess.normal[ndx+2] =
-	tess.normal[ndx+3] = R_VboPackNormal(normal);
+	tess.normal[ndx+3] = R_VaoPackNormal(normal);
 
 	// standard square texture coordinates
 	VectorSet2(tess.texCoords[ndx  ][0], s1, t1);
@@ -208,11 +207,9 @@ void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4])
 	tess.minIndex = 0;
 	tess.maxIndex = 3;
 
-	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
 
-	GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
-
-	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	tess.numIndexes = 0;
 	tess.numVertexes = 0;
@@ -353,7 +350,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 	glIndex_t      *outIndex;
 	float		*color;
 
-	RB_CheckVBOandIBO(tess.vbo, tess.ibo);
+	RB_CheckVao(tess.vao);
 
 	RB_CHECKOVERFLOW( numVerts, numIndexes );
 
@@ -377,7 +374,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 		dv = verts;
 		normal = &tess.normal[ tess.numVertexes ];
 		for ( i = 0 ; i < numVerts ; i++, dv++, normal++ )
-			*normal = R_VboPackNormal(dv->normal);
+			*normal = R_VaoPackNormal(dv->normal);
 	}
 
 #ifdef USE_VERT_TANGENT_SPACE
@@ -386,7 +383,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 		dv = verts;
 		tangent = &tess.tangent[ tess.numVertexes ];
 		for ( i = 0 ; i < numVerts ; i++, dv++, tangent++ )
-			*tangent = R_VboPackTangent(dv->tangent);
+			*tangent = R_VaoPackTangent(dv->tangent);
 	}
 #endif
 
@@ -419,7 +416,7 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 		dv = verts;
 		lightdir = &tess.lightdir[ tess.numVertexes ];
 		for ( i = 0 ; i < numVerts ; i++, dv++, lightdir++ )
-			*lightdir = R_VboPackNormal(dv->lightdir);
+			*lightdir = R_VaoPackNormal(dv->lightdir);
 	}
 
 #if 0  // nothing even uses vertex dlightbits
@@ -434,12 +431,12 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn
 	tess.numVertexes += numVerts;
 }
 
-static qboolean RB_SurfaceVbo(VBO_t *vbo, IBO_t *ibo, int numVerts, int numIndexes, int firstIndex, int minIndex, int maxIndex, int dlightBits, int pshadowBits, qboolean shaderCheck)
+static qboolean RB_SurfaceVao(vao_t *vao, int numVerts, int numIndexes, int firstIndex, int minIndex, int maxIndex, int dlightBits, int pshadowBits, qboolean shaderCheck)
 {
 	int i, mergeForward, mergeBack;
 	GLvoid *firstIndexOffset, *lastIndexOffset;
 
-	if (!vbo || !ibo)
+	if (!vao)
 	{
 		return qfalse;
 	}
@@ -449,7 +446,7 @@ static qboolean RB_SurfaceVbo(VBO_t *vbo, IBO_t *ibo, int numVerts, int numIndex
 		return qfalse;
 	}
 
-	RB_CheckVBOandIBO(vbo, ibo);
+	RB_CheckVao(vao);
 
 	tess.dlightBits |= dlightBits;
 	tess.pshadowBits |= pshadowBits;
@@ -543,7 +540,7 @@ RB_SurfaceTriangles
 =============
 */
 static void RB_SurfaceTriangles( srfBspSurface_t *srf ) {
-	if( RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes,
+	if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes,
 				srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) )
 	{
 		return;
@@ -627,16 +624,15 @@ static void RB_SurfaceBeam( void ) {
 	tess.maxIndex = tess.numVertexes;
 
 	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
-	RB_UpdateVBOs(ATTR_POSITION);
+	RB_UpdateTessVao(ATTR_POSITION);
 	
-	GLSL_VertexAttribsState(ATTR_POSITION);
 	GLSL_BindProgram(sp);
 		
 	GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
 
 	GLSL_SetUniformVec4(sp, UNIFORM_COLOR, colorRed);
 
-	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
+	R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
 
 	tess.numIndexes = 0;
 	tess.numVertexes = 0;
@@ -1173,7 +1169,7 @@ static void LerpMeshVertexes_scalar(mdvSurface_t *surf, float backlerp)
 			VectorCopy(newVerts->xyz,    outXyz);
 			VectorCopy(newVerts->normal, normal);
 
-			*outNormal = R_VboPackNormal(normal);
+			*outNormal = R_VaoPackNormal(normal);
 
 			newVerts++;
 			outXyz += 4;
@@ -1198,7 +1194,7 @@ static void LerpMeshVertexes_scalar(mdvSurface_t *surf, float backlerp)
 			VectorLerp(newVerts->normal, oldVerts->normal, backlerp, normal);
 			VectorNormalize(normal);
 
-			*outNormal = R_VboPackNormal(normal);
+			*outNormal = R_VaoPackNormal(normal);
 
 			newVerts++;
 			oldVerts++;
@@ -1365,7 +1361,7 @@ static void LerpCMeshVertexes( mdcSurface_t *surf, float backlerp ) {
 				fNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK];
 			}
 
-			*outNormal = R_VboPackNormal(fNormal);
+			*outNormal = R_VaoPackNormal(fNormal);
 		}
 	} else {
 		//
@@ -1435,7 +1431,7 @@ static void LerpCMeshVertexes( mdcSurface_t *surf, float backlerp ) {
 
 			VectorNormalize( fNormal );
 			
-			*outNormal = R_VboPackNormal(fNormal);
+			*outNormal = R_VaoPackNormal(fNormal);
 		}
 	}
 }
@@ -1499,7 +1495,7 @@ RB_SurfaceFace
 ==============
 */
 static void RB_SurfaceFace( srfBspSurface_t *srf ) {
-	if( RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes,
+	if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes,
 					srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) )
 	{
 		return;
@@ -1569,7 +1565,7 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) {
 	int     pshadowBits;
 	//int		*vDlightBits;
 
-	if( RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes,
+	if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes,
 					srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) )
 	{
 		return;
@@ -1662,13 +1658,13 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) {
 
 				if ( tess.shader->vertexAttribs & ATTR_NORMAL )
 				{
-					*normal++ = R_VboPackNormal(dv->normal);
+					*normal++ = R_VaoPackNormal(dv->normal);
 				}
 
 #ifdef USE_VERT_TANGENT_SPACE
 				if ( tess.shader->vertexAttribs & ATTR_TANGENT )
 				{
-					*tangent++ = R_VboPackTangent(dv->tangent);
+					*tangent++ = R_VaoPackTangent(dv->tangent);
 				}
 #endif
 
@@ -1692,7 +1688,7 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) {
 
 				if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION )
 				{
-					*lightdir++ = R_VboPackNormal(dv->lightdir);
+					*lightdir++ = R_VaoPackNormal(dv->lightdir);
 				}
 
 				//*vDlightBits++ = dlightBits;
@@ -1818,31 +1814,30 @@ static void RB_SurfaceFlare( srfFlare_t *surf ) {
 		RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, 1.0f, surf->normal, 0, qtrue);
 }
 
-static void RB_SurfaceVBOMesh(srfBspSurface_t * srf)
+static void RB_SurfaceVaoMesh(srfBspSurface_t * srf)
 {
-	RB_SurfaceVbo (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes, srf->firstIndex,
+	RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes, srf->firstIndex,
 			srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qfalse );
 }
 
-void RB_SurfaceVBOMDVMesh(srfVBOMDVMesh_t * surface)
+void RB_SurfaceVaoMdvMesh(srfVaoMdvMesh_t * surface)
 {
 	//mdvModel_t     *mdvModel;
 	//mdvSurface_t   *mdvSurface;
 	refEntity_t    *refEnt;
 
-	GLimp_LogComment("--- RB_SurfaceVBOMDVMesh ---\n");
+	GLimp_LogComment("--- RB_SurfaceVaoMdvMesh ---\n");
 
-	if(!surface->vbo || !surface->ibo)
+	if(!surface->vao)
 		return;
 
-	//RB_CheckVBOandIBO(surface->vbo, surface->ibo);
+	//RB_CheckVao(surface->vao);
 	RB_EndSurface();
 	RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex);
 
-	R_BindVBO(surface->vbo);
-	R_BindIBO(surface->ibo);
+	R_BindVao(surface->vao);
 
-	tess.useInternalVBO = qfalse;
+	tess.useInternalVao = qfalse;
 
 	tess.numIndexes += surface->numIndexes;
 	tess.numVertexes += surface->numVerts;
@@ -1865,7 +1860,8 @@ void RB_SurfaceVBOMDVMesh(srfVBOMDVMesh_t * surface)
 
 	glState.vertexAttribsOldFrame = refEnt->oldframe;
 	glState.vertexAttribsNewFrame = refEnt->frame;
-	glState.vertexAnimation = qtrue;
+	if (surface->mdvModel->numFrames > 1)
+		glState.vertexAnimation = qtrue;
 
 	RB_EndSurface();
 
@@ -1898,6 +1894,6 @@ void( *rb_surfaceTable[SF_NUM_SURFACE_TYPES] ) ( void * ) = {
 	( void( * ) ( void* ) )RB_SurfaceFlare,		// SF_FLARE,
 	( void( * ) ( void* ) )RB_SurfaceEntity,	// SF_ENTITY
 	( void( * ) ( void* ) )RB_SurfaceDisplayList,	// SF_DISPLAY_LIST
-	( void( * ) ( void* ) )RB_SurfaceVBOMesh,	// SF_VBO_MESH,
-	( void( * ) ( void* ) )RB_SurfaceVBOMDVMesh,	// SF_VBO_MDVMESH
+	( void( * ) ( void* ) )RB_SurfaceVaoMesh,	// SF_VAO_MESH,
+	( void( * ) ( void* ) )RB_SurfaceVaoMdvMesh,	// SF_VAO_MDVMESH
 };
diff --git a/SP/code/rend2/tr_vbo.c b/SP/code/rend2/tr_vbo.c
index dc0f33b..5c44b0e 100644
--- a/SP/code/rend2/tr_vbo.c
+++ b/SP/code/rend2/tr_vbo.c
@@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 #include "tr_local.h"
 
 
-uint32_t R_VboPackTangent(vec4_t v)
+uint32_t R_VaoPackTangent(vec4_t v)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -41,7 +41,7 @@ uint32_t R_VboPackTangent(vec4_t v)
 	}
 }
 
-uint32_t R_VboPackNormal(vec3_t v)
+uint32_t R_VaoPackNormal(vec3_t v)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -57,7 +57,7 @@ uint32_t R_VboPackNormal(vec3_t v)
 	}
 }
 
-void R_VboUnpackTangent(vec4_t v, uint32_t b)
+void R_VaoUnpackTangent(vec4_t v, uint32_t b)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -75,7 +75,7 @@ void R_VboUnpackTangent(vec4_t v, uint32_t b)
 	}
 }
 
-void R_VboUnpackNormal(vec3_t v, uint32_t b)
+void R_VaoUnpackNormal(vec3_t v, uint32_t b)
 {
 	if (glRefConfig.packedNormalDataType == GL_UNSIGNED_INT_2_10_10_10_REV)
 	{
@@ -91,790 +91,530 @@ void R_VboUnpackNormal(vec3_t v, uint32_t b)
 	}
 }
 
+
+void Vao_SetVertexPointers(vao_t *vao)
+{
+	int i;
+
+	// set vertex pointers
+	for (i = 0; i < ATTR_INDEX_COUNT; i++)
+	{
+		if (vao->attribs[i].enabled)
+		{
+			qglVertexAttribPointerARB((GLuint)i,
+								  (GLint)vao->attribs[i].count,
+								  (GLenum)vao->attribs[i].type, 
+								  (GLboolean)vao->attribs[i].normalized, 
+								  (GLsizei)vao->attribs[i].stride,
+								  BUFFER_OFFSET(vao->attribs[i].offset));
+			qglEnableVertexAttribArrayARB(i);
+		}
+		else
+		{
+			qglDisableVertexAttribArrayARB(i);
+		}
+	}
+}
+
 /*
 ============
-R_CreateVBO
+R_CreateVao
 ============
 */
-VBO_t          *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage)
+vao_t *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage)
 {
-	VBO_t          *vbo;
+	vao_t          *vao;
 	int				glUsage;
 
 	switch (usage)
 	{
-		case VBO_USAGE_STATIC:
+		case VAO_USAGE_STATIC:
 			glUsage = GL_STATIC_DRAW_ARB;
 			break;
 
-		case VBO_USAGE_DYNAMIC:
+		case VAO_USAGE_DYNAMIC:
 			glUsage = GL_DYNAMIC_DRAW_ARB;
 			break;
 
 		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
+			Com_Error(ERR_FATAL, "bad vaoUsage_t given: %i", usage);
 			return NULL;
 	}
 
 	if(strlen(name) >= MAX_QPATH)
 	{
-		ri.Error(ERR_DROP, "R_CreateVBO: \"%s\" is too long", name);
+		ri.Error(ERR_DROP, "R_CreateVao: \"%s\" is too long", name);
 	}
 
-	if ( tr.numVBOs == MAX_VBOS ) {
-		ri.Error( ERR_DROP, "R_CreateVBO: MAX_VBOS hit");
+	if ( tr.numVaos == MAX_VAOS ) {
+		ri.Error( ERR_DROP, "R_CreateVao: MAX_VAOS hit");
 	}
 
 	R_IssuePendingRenderCommands();
 
-	vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low);
-	tr.numVBOs++;
+	vao = tr.vaos[tr.numVaos] = ri.Hunk_Alloc(sizeof(*vao), h_low);
+	tr.numVaos++;
+
+	memset(vao, 0, sizeof(*vao));
+
+	Q_strncpyz(vao->name, name, sizeof(vao->name));
+
 
-	memset(vbo, 0, sizeof(*vbo));
+	if (glRefConfig.vertexArrayObject)
+	{
+		qglGenVertexArraysARB(1, &vao->vao);
+		qglBindVertexArrayARB(vao->vao);
+	}
 
-	Q_strncpyz(vbo->name, name, sizeof(vbo->name));
 
-	vbo->vertexesSize = vertexesSize;
+	vao->vertexesSize = vertexesSize;
 
-	qglGenBuffersARB(1, &vbo->vertexesVBO);
+	qglGenBuffersARB(1, &vao->vertexesVBO);
 
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
 	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexesSize, vertexes, glUsage);
 
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
 
-	glState.currentVBO = NULL;
+	vao->indexesSize = indexesSize;
+
+	qglGenBuffersARB(1, &vao->indexesIBO);
+
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesIBO);
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
+
+
+	glState.currentVao = vao;
 
 	GL_CheckErrors();
 
-	return vbo;
+	return vao;
 }
 
 /*
 ============
-R_CreateVBO2
+R_CreateVao2
 ============
 */
-VBO_t          *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * verts, unsigned int stateBits, vboUsage_t usage)
+vao_t *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *indexes)
 {
-	VBO_t          *vbo;
+	vao_t          *vao;
 	int             i;
 
 	byte           *data;
 	int             dataSize;
 	int             dataOfs;
 
-	int				glUsage;
-
-	switch (usage)
-	{
-		case VBO_USAGE_STATIC:
-			glUsage = GL_STATIC_DRAW_ARB;
-			break;
+	int				glUsage = GL_STATIC_DRAW_ARB;
 
-		case VBO_USAGE_DYNAMIC:
-			glUsage = GL_DYNAMIC_DRAW_ARB;
-			break;
-
-		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
-			return NULL;
-	}
-
-	if(!numVertexes)
+	if(!numVertexes || !numIndexes)
 		return NULL;
 
 	if(strlen(name) >= MAX_QPATH)
 	{
-		ri.Error(ERR_DROP, "R_CreateVBO2: \"%s\" is too long", name);
+		ri.Error(ERR_DROP, "R_CreateVao2: \"%s\" is too long", name);
 	}
 
-	if ( tr.numVBOs == MAX_VBOS ) {
-		ri.Error( ERR_DROP, "R_CreateVBO2: MAX_VBOS hit");
+	if ( tr.numVaos == MAX_VAOS ) {
+		ri.Error( ERR_DROP, "R_CreateVao2: MAX_VAOS hit");
 	}
 
 	R_IssuePendingRenderCommands();
 
-	vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low);
-	tr.numVBOs++;
-
-	memset(vbo, 0, sizeof(*vbo));
-
-	Q_strncpyz(vbo->name, name, sizeof(vbo->name));
-
-	if (usage == VBO_USAGE_STATIC)
-	{
-		// since these vertex attributes are never altered, interleave them
-		vbo->ofs_xyz = 0;
-		dataSize = sizeof(verts[0].xyz);
-
-		if(stateBits & ATTR_NORMAL)
-		{
-			vbo->ofs_normal = dataSize;
-			dataSize += sizeof(uint32_t);
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(stateBits & ATTR_TANGENT)
-		{
-			vbo->ofs_tangent = dataSize;
-			dataSize += sizeof(uint32_t);
-		}
-#endif
-
-		if(stateBits & ATTR_TEXCOORD)
-		{
-			vbo->ofs_st = dataSize;
-			dataSize += sizeof(verts[0].st);
-		}
-
-		if(stateBits & ATTR_LIGHTCOORD)
-		{
-			vbo->ofs_lightmap = dataSize;
-			dataSize += sizeof(verts[0].lightmap);
-		}
+	vao = tr.vaos[tr.numVaos] = ri.Hunk_Alloc(sizeof(*vao), h_low);
+	tr.numVaos++;
 
-		if(stateBits & ATTR_COLOR)
-		{
-			vbo->ofs_vertexcolor = dataSize;
-			dataSize += sizeof(verts[0].vertexColors);
-		}
+	memset(vao, 0, sizeof(*vao));
 
-		if(stateBits & ATTR_LIGHTDIRECTION)
-		{
-			vbo->ofs_lightdir = dataSize;
-			dataSize += sizeof(uint32_t);
-		}
+	Q_strncpyz(vao->name, name, sizeof(vao->name));
 
-		vbo->stride_xyz         = dataSize;
-		vbo->stride_normal      = dataSize;
+	// since these vertex attributes are never altered, interleave them
+	vao->attribs[ATTR_INDEX_POSITION      ].enabled = 1;
+	vao->attribs[ATTR_INDEX_NORMAL        ].enabled = 1;
 #ifdef USE_VERT_TANGENT_SPACE
-		vbo->stride_tangent     = dataSize;
+	vao->attribs[ATTR_INDEX_TANGENT       ].enabled = 1;
 #endif
-		vbo->stride_st          = dataSize;
-		vbo->stride_lightmap    = dataSize;
-		vbo->stride_vertexcolor = dataSize;
-		vbo->stride_lightdir    = dataSize;
-
-		// create VBO
-		dataSize *= numVertexes;
-		data = ri.Hunk_AllocateTempMemory(dataSize);
-		dataOfs = 0;
-
-		//ri.Printf(PRINT_ALL, "CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor,
-			//vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor);
-
-		for (i = 0; i < numVertexes; i++)
-		{
-			// xyz
-			memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
-			dataOfs += sizeof(verts[i].xyz);
-
-			// normal
-			if(stateBits & ATTR_NORMAL)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackNormal(verts[i].normal);
-
-				dataOfs += sizeof(uint32_t);
-			}
-
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].enabled = 1;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].enabled = 1;
+	vao->attribs[ATTR_INDEX_COLOR         ].enabled = 1;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].count = 3;
+	vao->attribs[ATTR_INDEX_NORMAL        ].count = 4;
+	vao->attribs[ATTR_INDEX_TANGENT       ].count = 4;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].count = 2;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].count = 2;
+	vao->attribs[ATTR_INDEX_COLOR         ].count = 4;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_NORMAL        ].type = glRefConfig.packedNormalDataType;
+	vao->attribs[ATTR_INDEX_TANGENT       ].type = glRefConfig.packedNormalDataType;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_COLOR         ].type = GL_FLOAT;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = glRefConfig.packedNormalDataType;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_NORMAL        ].normalized = GL_TRUE;
+	vao->attribs[ATTR_INDEX_TANGENT       ].normalized = GL_TRUE;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_COLOR         ].normalized = GL_FALSE;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+
+	vao->attribs[ATTR_INDEX_POSITION      ].offset = 0;        dataSize  = sizeof(verts[0].xyz);
+	vao->attribs[ATTR_INDEX_NORMAL        ].offset = dataSize; dataSize += sizeof(uint32_t);
 #ifdef USE_VERT_TANGENT_SPACE
-			// tangent
-			if(stateBits & ATTR_TANGENT)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackTangent(verts[i].tangent);
-
-				dataOfs += sizeof(uint32_t);
-			}
+	vao->attribs[ATTR_INDEX_TANGENT       ].offset = dataSize; dataSize += sizeof(uint32_t);
 #endif
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].offset = dataSize; dataSize += sizeof(verts[0].st);
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].offset = dataSize; dataSize += sizeof(verts[0].lightmap);
+	vao->attribs[ATTR_INDEX_COLOR         ].offset = dataSize; dataSize += sizeof(verts[0].vertexColors);
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(uint32_t);
 
-			// vertex texcoords
-			if(stateBits & ATTR_TEXCOORD)
-			{
-				memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
-				dataOfs += sizeof(verts[i].st);
-			}
-
-			// feed vertex lightmap texcoords
-			if(stateBits & ATTR_LIGHTCOORD)
-			{
-				memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
-				dataOfs += sizeof(verts[i].lightmap);
-			}
-
-			// feed vertex colors
-			if(stateBits & ATTR_COLOR)
-			{
-				memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
-				dataOfs += sizeof(verts[i].vertexColors);
-			}
-
-			// feed vertex light directions
-			if(stateBits & ATTR_LIGHTDIRECTION)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
+	vao->attribs[ATTR_INDEX_POSITION      ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_NORMAL        ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_TANGENT       ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_TEXCOORD      ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_LIGHTCOORD    ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_COLOR         ].stride = dataSize;
+	vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize;
 
-				*p = R_VboPackNormal(verts[i].lightdir);
 
-				dataOfs += sizeof(uint32_t);
-			}
-		}
-	}
-	else
+	if (glRefConfig.vertexArrayObject)
 	{
-		// since these vertex attributes may be changed, put them in flat arrays
-		dataSize = sizeof(verts[0].xyz);
-
-		if(stateBits & ATTR_NORMAL)
-		{
-			dataSize += sizeof(uint32_t);
-		}
-
-#ifdef USE_VERT_TANGENT_SPACE
-		if(stateBits & ATTR_TANGENT)
-		{
-			dataSize += sizeof(uint32_t);
-		}
-#endif
-
-		if(stateBits & ATTR_TEXCOORD)
-		{
-			dataSize += sizeof(verts[0].st);
-		}
-
-		if(stateBits & ATTR_LIGHTCOORD)
-		{
-			dataSize += sizeof(verts[0].lightmap);
-		}
-
-		if(stateBits & ATTR_COLOR)
-		{
-			dataSize += sizeof(verts[0].vertexColors);
-		}
-
-		if(stateBits & ATTR_LIGHTDIRECTION)
-		{
-			dataSize += sizeof(uint32_t);
-		}
+		qglGenVertexArraysARB(1, &vao->vao);
+		qglBindVertexArrayARB(vao->vao);
+	}
 
-		// create VBO
-		dataSize *= numVertexes;
-		data = ri.Hunk_AllocateTempMemory(dataSize);
-		dataOfs = 0;
 
-		vbo->ofs_xyz            = 0;
-		vbo->ofs_normal         = 0;
-#ifdef USE_VERT_TANGENT_SPACE
-		vbo->ofs_tangent        = 0;
-#endif
-		vbo->ofs_st             = 0;
-		vbo->ofs_lightmap       = 0;
-		vbo->ofs_vertexcolor    = 0;
-		vbo->ofs_lightdir       = 0;
+	// create VBO
+	dataSize *= numVertexes;
+	data = ri.Hunk_AllocateTempMemory(dataSize);
+	dataOfs = 0;
 
-		vbo->stride_xyz         = sizeof(verts[0].xyz);
-		vbo->stride_normal      = sizeof(uint32_t);
-#ifdef USE_VERT_TANGENT_SPACE
-		vbo->stride_tangent     = sizeof(uint32_t);
-#endif
-		vbo->stride_vertexcolor = sizeof(verts[0].vertexColors);
-		vbo->stride_st          = sizeof(verts[0].st);
-		vbo->stride_lightmap    = sizeof(verts[0].lightmap);
-		vbo->stride_lightdir    = sizeof(uint32_t);
-
-		//ri.Printf(PRINT_ALL, "2CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor,
-			//vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor);
+	for (i = 0; i < numVertexes; i++)
+	{
+		uint32_t *p;
 
 		// xyz
-		for (i = 0; i < numVertexes; i++)
-		{
-			memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
-			dataOfs += sizeof(verts[i].xyz);
-		}
+		memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
+		dataOfs += sizeof(verts[i].xyz);
 
 		// normal
-		if(stateBits & ATTR_NORMAL)
-		{
-			vbo->ofs_normal = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackNormal(verts[i].normal);
-
-				dataOfs += sizeof(uint32_t);
-			}
-		}
+		p = (uint32_t *)(data + dataOfs);
+		*p = R_VaoPackNormal(verts[i].normal);
+		dataOfs += sizeof(uint32_t);
 
 #ifdef USE_VERT_TANGENT_SPACE
 		// tangent
-		if(stateBits & ATTR_TANGENT)
-		{
-			vbo->ofs_tangent = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackTangent(verts[i].tangent);
-
-				dataOfs += sizeof(uint32_t);
-			}
-		}
+		p = (uint32_t *)(data + dataOfs);
+		*p = R_VaoPackTangent(verts[i].tangent);
+		dataOfs += sizeof(uint32_t);
 #endif
 
 		// vertex texcoords
-		if(stateBits & ATTR_TEXCOORD)
-		{
-			vbo->ofs_st = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
-				dataOfs += sizeof(verts[i].st);
-			}
-		}
+		memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
+		dataOfs += sizeof(verts[i].st);
 
 		// feed vertex lightmap texcoords
-		if(stateBits & ATTR_LIGHTCOORD)
-		{
-			vbo->ofs_lightmap = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
-				dataOfs += sizeof(verts[i].lightmap);
-			}
-		}
+		memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
+		dataOfs += sizeof(verts[i].lightmap);
 
 		// feed vertex colors
-		if(stateBits & ATTR_COLOR)
-		{
-			vbo->ofs_vertexcolor = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
-				dataOfs += sizeof(verts[i].vertexColors);
-			}
-		}
-
-		// feed vertex lightdirs
-		if(stateBits & ATTR_LIGHTDIRECTION)
-		{
-			vbo->ofs_lightdir = dataOfs;
-			for (i = 0; i < numVertexes; i++)
-			{
-				uint32_t *p = (uint32_t *)(data + dataOfs);
-
-				*p = R_VboPackNormal(verts[i].lightdir);
-
-				dataOfs += sizeof(uint32_t);
-			}
-		}
-	}
-
-
-	vbo->vertexesSize = dataSize;
-
-	qglGenBuffersARB(1, &vbo->vertexesVBO);
-
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
-	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, data, glUsage);
-
-	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
-
-	glState.currentVBO = NULL;
-
-	GL_CheckErrors();
-
-	ri.Hunk_FreeTempMemory(data);
+		memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
+		dataOfs += sizeof(verts[i].vertexColors);
 
-	return vbo;
-}
-
-
-/*
-============
-R_CreateIBO
-============
-*/
-IBO_t          *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage)
-{
-	IBO_t          *ibo;
-	int				glUsage;
-
-	switch (usage)
-	{
-		case VBO_USAGE_STATIC:
-			glUsage = GL_STATIC_DRAW_ARB;
-			break;
-
-		case VBO_USAGE_DYNAMIC:
-			glUsage = GL_DYNAMIC_DRAW_ARB;
-			break;
-
-		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
-			return NULL;
-	}
-
-	if(strlen(name) >= MAX_QPATH)
-	{
-		ri.Error(ERR_DROP, "R_CreateIBO: \"%s\" is too long", name);
-	}
-
-	if ( tr.numIBOs == MAX_IBOS ) {
-		ri.Error( ERR_DROP, "R_CreateIBO: MAX_IBOS hit");
-	}
-
-	R_IssuePendingRenderCommands();
-
-	ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
-	tr.numIBOs++;
-
-	Q_strncpyz(ibo->name, name, sizeof(ibo->name));
-
-	ibo->indexesSize = indexesSize;
-
-	qglGenBuffersARB(1, &ibo->indexesVBO);
-
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
-	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
-
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-
-	glState.currentIBO = NULL;
-
-	GL_CheckErrors();
-
-	return ibo;
-}
-
-/*
-============
-R_CreateIBO2
-============
-*/
-IBO_t          *R_CreateIBO2(const char *name, int numIndexes, glIndex_t * inIndexes, vboUsage_t usage)
-{
-	IBO_t          *ibo;
-	int             i;
-
-	glIndex_t       *indexes;
-	int             indexesSize;
-
-	int				glUsage;
-
-	switch (usage)
-	{
-		case VBO_USAGE_STATIC:
-			glUsage = GL_STATIC_DRAW_ARB;
-			break;
-
-		case VBO_USAGE_DYNAMIC:
-			glUsage = GL_DYNAMIC_DRAW_ARB;
-			break;
-
-		default:
-			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
-			return NULL;
+		// feed vertex light directions
+		p = (uint32_t *)(data + dataOfs);
+		*p = R_VaoPackNormal(verts[i].lightdir);
+		dataOfs += sizeof(uint32_t);
 	}
 
-	if(!numIndexes)
-		return NULL;
+	vao->vertexesSize = dataSize;
 
-	if(strlen(name) >= MAX_QPATH)
-	{
-		ri.Error(ERR_DROP, "R_CreateIBO2: \"%s\" is too long", name);
-	}
+	qglGenBuffersARB(1, &vao->vertexesVBO);
 
-	if ( tr.numIBOs == MAX_IBOS ) {
-		ri.Error( ERR_DROP, "R_CreateIBO2: MAX_IBOS hit");
-	}
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
+	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vao->vertexesSize, data, glUsage);
 
-	R_IssuePendingRenderCommands();
 
-	ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
-	tr.numIBOs++;
+	// create IBO
+	vao->indexesSize = numIndexes * sizeof(glIndex_t);
 
-	Q_strncpyz(ibo->name, name, sizeof(ibo->name));
+	qglGenBuffersARB(1, &vao->indexesIBO);
 
-	indexesSize = numIndexes * sizeof(glIndex_t);
-	indexes = ri.Hunk_AllocateTempMemory(indexesSize);
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesIBO);
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesSize, indexes, glUsage);
 
-	for(i = 0; i < numIndexes; i++)
-	{
-		indexes[i] = inIndexes[i];
-	}
-
-	ibo->indexesSize = indexesSize;
 
-	qglGenBuffersARB(1, &ibo->indexesVBO);
+	Vao_SetVertexPointers(vao);
 
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
-	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
 
-	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-
-	glState.currentIBO = NULL;
+	glState.currentVao = vao;
 
 	GL_CheckErrors();
 
-	ri.Hunk_FreeTempMemory(indexes);
+	ri.Hunk_FreeTempMemory(data);
 
-	return ibo;
+	return vao;
 }
 
+
 /*
 ============
-R_BindVBO
+R_BindVao
 ============
 */
-void R_BindVBO(VBO_t * vbo)
+void R_BindVao(vao_t * vao)
 {
-	if(!vbo)
+	if(!vao)
 	{
-		//R_BindNullVBO();
-		ri.Error(ERR_DROP, "R_BindNullVBO: NULL vbo");
+		//R_BindNullVao();
+		ri.Error(ERR_DROP, "R_BindVao: NULL vao");
 		return;
 	}
 
 	if(r_logFile->integer)
 	{
 		// don't just call LogComment, or we will get a call to va() every frame!
-		GLimp_LogComment(va("--- R_BindVBO( %s ) ---\n", vbo->name));
+		GLimp_LogComment(va("--- R_BindVao( %s ) ---\n", vao->name));
 	}
 
-	if(glState.currentVBO != vbo)
+	if(glState.currentVao != vao)
 	{
-		glState.currentVBO = vbo;
-		glState.vertexAttribPointersSet = 0;
+		glState.currentVao = vao;
 
 		glState.vertexAttribsInterpolation = 0;
 		glState.vertexAttribsOldFrame = 0;
 		glState.vertexAttribsNewFrame = 0;
 		glState.vertexAnimation = qfalse;
+		backEnd.pc.c_vaoBinds++;
 
-		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
-
-		backEnd.pc.c_vboVertexBuffers++;
-	}
-}
+		if (glRefConfig.vertexArrayObject)
+		{
+			qglBindVertexArrayARB(vao->vao);
 
-/*
-============
-R_BindNullVBO
-============
-*/
-void R_BindNullVBO(void)
-{
-	GLimp_LogComment("--- R_BindNullVBO ---\n");
+			// why you no save GL_ELEMENT_ARRAY_BUFFER binding, Intel?
+			if (1)
+				qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO);
+		}
+		else
+		{
+			qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vao->vertexesVBO);
+			qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vao->indexesIBO);
 
-	if(glState.currentVBO)
-	{
-		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
-		glState.currentVBO = NULL;
+			Vao_SetVertexPointers(vao);
+		}
 	}
-
-	GL_CheckErrors();
 }
 
 /*
 ============
-R_BindIBO
+R_BindNullVao
 ============
 */
-void R_BindIBO(IBO_t * ibo)
+void R_BindNullVao(void)
 {
-	if(!ibo)
-	{
-		//R_BindNullIBO();
-		ri.Error(ERR_DROP, "R_BindIBO: NULL ibo");
-		return;
-	}
+	GLimp_LogComment("--- R_BindNullVao ---\n");
 
-	if(r_logFile->integer)
+	if(glState.currentVao)
 	{
-		// don't just call LogComment, or we will get a call to va() every frame!
-		GLimp_LogComment(va("--- R_BindIBO( %s ) ---\n", ibo->name));
-	}
-
-	if(glState.currentIBO != ibo)
-	{
-		qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
-
-		glState.currentIBO = ibo;
+		if (glRefConfig.vertexArrayObject)
+		{
+			qglBindVertexArrayARB(0);
 
-		backEnd.pc.c_vboIndexBuffers++;
+			// why you no save GL_ELEMENT_ARRAY_BUFFER binding, Intel?
+			if (1) qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+		}
+		else
+		{
+			qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
+			qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+		}
+		glState.currentVao = NULL;
 	}
-}
 
-/*
-============
-R_BindNullIBO
-============
-*/
-void R_BindNullIBO(void)
-{
-	GLimp_LogComment("--- R_BindNullIBO ---\n");
-
-	if(glState.currentIBO)
-	{
-		qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-		glState.currentIBO = NULL;
-		glState.vertexAttribPointersSet = 0;
-	}
+	GL_CheckErrors();
 }
 
+
 /*
 ============
-R_InitVBOs
+R_InitVaos
 ============
 */
-void R_InitVBOs(void)
+void R_InitVaos(void)
 {
-	int             dataSize;
+	int             vertexesSize, indexesSize;
 	int             offset;
 
-	ri.Printf(PRINT_ALL, "------- R_InitVBOs -------\n");
+	ri.Printf(PRINT_ALL, "------- R_InitVaos -------\n");
 
-	tr.numVBOs = 0;
-	tr.numIBOs = 0;
+	tr.numVaos = 0;
 
-	dataSize  = sizeof(tess.xyz[0]);
-	dataSize += sizeof(tess.normal[0]);
+	vertexesSize  = sizeof(tess.xyz[0]);
+	vertexesSize += sizeof(tess.normal[0]);
 #ifdef USE_VERT_TANGENT_SPACE
-	dataSize += sizeof(tess.tangent[0]);
+	vertexesSize += sizeof(tess.tangent[0]);
 #endif
-	dataSize += sizeof(tess.vertexColors[0]);
-	dataSize += sizeof(tess.texCoords[0][0]) * 2;
-	dataSize += sizeof(tess.lightdir[0]);
-	dataSize *= SHADER_MAX_VERTEXES;
+	vertexesSize += sizeof(tess.vertexColors[0]);
+	vertexesSize += sizeof(tess.texCoords[0][0]) * 2;
+	vertexesSize += sizeof(tess.lightdir[0]);
+	vertexesSize *= SHADER_MAX_VERTEXES;
 
-	tess.vbo = R_CreateVBO("tessVertexArray_VBO", NULL, dataSize, VBO_USAGE_DYNAMIC);
+	indexesSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES;
+
+	tess.vao = R_CreateVao("tessVertexArray_VAO", NULL, vertexesSize, NULL, indexesSize, VAO_USAGE_DYNAMIC);
 
 	offset = 0;
 
-	tess.vbo->ofs_xyz         = offset; offset += sizeof(tess.xyz[0])              * SHADER_MAX_VERTEXES;
-	tess.vbo->ofs_normal      = offset; offset += sizeof(tess.normal[0])           * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].enabled = 1;
+#ifdef USE_VERT_TANGENT_SPACE
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].enabled = 1;
+#endif
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].enabled = 1;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].count = 3;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].count = 4;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].count = 4;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].count = 2;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].count = 2;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].count = 4;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].type = glRefConfig.packedNormalDataType;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].type = glRefConfig.packedNormalDataType;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].type = GL_FLOAT;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = glRefConfig.packedNormalDataType;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].normalized = GL_TRUE;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].normalized = GL_TRUE;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].normalized = GL_FALSE;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].offset = offset; offset += sizeof(tess.xyz[0])              * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].offset = offset; offset += sizeof(tess.normal[0])           * SHADER_MAX_VERTEXES;
 #ifdef USE_VERT_TANGENT_SPACE
-	tess.vbo->ofs_tangent     = offset; offset += sizeof(tess.tangent[0])          * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].offset = offset; offset += sizeof(tess.tangent[0])          * SHADER_MAX_VERTEXES;
 #endif
 	// these next two are actually interleaved
-	tess.vbo->ofs_st          = offset; 
-	tess.vbo->ofs_lightmap    = offset + sizeof(tess.texCoords[0][0]);
-	                                    offset += sizeof(tess.texCoords[0][0]) * 2 * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].offset = offset; 
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].offset = offset + sizeof(tess.texCoords[0][0]);
+	                                                              offset += sizeof(tess.texCoords[0][0]) * 2 * SHADER_MAX_VERTEXES;
 
-	tess.vbo->ofs_vertexcolor = offset; offset += sizeof(tess.vertexColors[0])     * SHADER_MAX_VERTEXES;
-	tess.vbo->ofs_lightdir    = offset;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].offset = offset; offset += sizeof(tess.vertexColors[0])     * SHADER_MAX_VERTEXES;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = offset;
 
-	tess.vbo->stride_xyz         = sizeof(tess.xyz[0]);
-	tess.vbo->stride_normal      = sizeof(tess.normal[0]);
+	tess.vao->attribs[ATTR_INDEX_POSITION      ].stride = sizeof(tess.xyz[0]);
+	tess.vao->attribs[ATTR_INDEX_NORMAL        ].stride = sizeof(tess.normal[0]);
 #ifdef USE_VERT_TANGENT_SPACE
-	tess.vbo->stride_tangent     = sizeof(tess.tangent[0]);
+	tess.vao->attribs[ATTR_INDEX_TANGENT       ].stride = sizeof(tess.tangent[0]);
 #endif
-	tess.vbo->stride_vertexcolor = sizeof(tess.vertexColors[0]);
-	tess.vbo->stride_st          = sizeof(tess.texCoords[0][0]) * 2;
-	tess.vbo->stride_lightmap    = sizeof(tess.texCoords[0][0]) * 2;
-	tess.vbo->stride_lightdir    = sizeof(tess.lightdir[0]);
-
-	dataSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES;
+	tess.vao->attribs[ATTR_INDEX_COLOR         ].stride = sizeof(tess.vertexColors[0]);
+	tess.vao->attribs[ATTR_INDEX_TEXCOORD      ].stride = sizeof(tess.texCoords[0][0]) * 2;
+	tess.vao->attribs[ATTR_INDEX_LIGHTCOORD    ].stride = sizeof(tess.texCoords[0][0]) * 2;
+	tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = sizeof(tess.lightdir[0]);
+
+	tess.attribPointers[ATTR_INDEX_POSITION]       = tess.xyz;
+	tess.attribPointers[ATTR_INDEX_TEXCOORD]       = tess.texCoords;
+	tess.attribPointers[ATTR_INDEX_NORMAL]         = tess.normal;
+#ifdef USE_VERT_TANGENT_SPACE
+	tess.attribPointers[ATTR_INDEX_TANGENT]        = tess.tangent;
+#endif
+	tess.attribPointers[ATTR_INDEX_COLOR]          = tess.vertexColors;
+	tess.attribPointers[ATTR_INDEX_LIGHTDIRECTION] = tess.lightdir;
 
-	tess.ibo = R_CreateIBO("tessVertexArray_IBO", NULL, dataSize, VBO_USAGE_DYNAMIC);
+	Vao_SetVertexPointers(tess.vao);
 
-	R_BindNullVBO();
-	R_BindNullIBO();
+	R_BindNullVao();
 
 	GL_CheckErrors();
 }
 
 /*
 ============
-R_ShutdownVBOs
+R_ShutdownVaos
 ============
 */
-void R_ShutdownVBOs(void)
+void R_ShutdownVaos(void)
 {
 	int             i;
-	VBO_t          *vbo;
-	IBO_t          *ibo;
-
-	ri.Printf(PRINT_ALL, "------- R_ShutdownVBOs -------\n");
+	vao_t          *vao;
 
-	R_BindNullVBO();
-	R_BindNullIBO();
+	ri.Printf(PRINT_ALL, "------- R_ShutdownVaos -------\n");
 
+	R_BindNullVao();
 
-	for(i = 0; i < tr.numVBOs; i++)
+	for(i = 0; i < tr.numVaos; i++)
 	{
-		vbo = tr.vbos[i];
+		vao = tr.vaos[i];
+
+		if(vao->vao)
+			qglDeleteVertexArraysARB(1, &vao->vao);
 
-		if(vbo->vertexesVBO)
+		if(vao->vertexesVBO)
 		{
-			qglDeleteBuffersARB(1, &vbo->vertexesVBO);
+			qglDeleteBuffersARB(1, &vao->vertexesVBO);
 		}
 
-		//ri.Free(vbo);
-	}
-
-	for(i = 0; i < tr.numIBOs; i++)
-	{
-		ibo = tr.ibos[i];
-
-		if(ibo->indexesVBO)
+		if(vao->indexesIBO)
 		{
-			qglDeleteBuffersARB(1, &ibo->indexesVBO);
+			qglDeleteBuffersARB(1, &vao->indexesIBO);
 		}
-
-		//ri.Free(ibo);
 	}
 
-	tr.numVBOs = 0;
-	tr.numIBOs = 0;
+	tr.numVaos = 0;
 }
 
 /*
 ============
-R_VBOList_f
+R_VaoList_f
 ============
 */
-void R_VBOList_f(void)
+void R_VaoList_f(void)
 {
 	int             i;
-	VBO_t          *vbo;
-	IBO_t          *ibo;
+	vao_t          *vao;
 	int             vertexesSize = 0;
 	int             indexesSize = 0;
 
 	ri.Printf(PRINT_ALL, " size          name\n");
 	ri.Printf(PRINT_ALL, "----------------------------------------------------------\n");
 
-	for(i = 0; i < tr.numVBOs; i++)
+	for(i = 0; i < tr.numVaos; i++)
 	{
-		vbo = tr.vbos[i];
+		vao = tr.vaos[i];
 
-		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vbo->vertexesSize / (1024 * 1024),
-				  (vbo->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vbo->name);
+		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->vertexesSize / (1024 * 1024),
+				  (vao->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name);
 
-		vertexesSize += vbo->vertexesSize;
+		vertexesSize += vao->vertexesSize;
 	}
 
-	for(i = 0; i < tr.numIBOs; i++)
+	for(i = 0; i < tr.numVaos; i++)
 	{
-		ibo = tr.ibos[i];
+		vao = tr.vaos[i];
 
-		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", ibo->indexesSize / (1024 * 1024),
-				  (ibo->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), ibo->name);
+		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->indexesSize / (1024 * 1024),
+				  (vao->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name);
 
-		indexesSize += ibo->indexesSize;
+		indexesSize += vao->indexesSize;
 	}
 
-	ri.Printf(PRINT_ALL, " %i total VBOs\n", tr.numVBOs);
+	ri.Printf(PRINT_ALL, " %i total VAOs\n", tr.numVaos);
 	ri.Printf(PRINT_ALL, " %d.%02d MB total vertices memory\n", vertexesSize / (1024 * 1024),
 			  (vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
-
-	ri.Printf(PRINT_ALL, " %i total IBOs\n", tr.numIBOs);
 	ri.Printf(PRINT_ALL, " %d.%02d MB total triangle indices memory\n", indexesSize / (1024 * 1024),
 			  (indexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
 }
@@ -882,89 +622,61 @@ void R_VBOList_f(void)
 
 /*
 ==============
-RB_UpdateVBOs
+RB_UpdateTessVao
 
 Adapted from Tess_UpdateVBOs from xreal
 
-Update the default VBO to replace the client side vertex arrays
+Update the default VAO to replace the client side vertex arrays
 ==============
 */
-void RB_UpdateVBOs(unsigned int attribBits)
+void RB_UpdateTessVao(unsigned int attribBits)
 {
-	GLimp_LogComment("--- RB_UpdateVBOs ---\n");
+	GLimp_LogComment("--- RB_UpdateTessVao ---\n");
 
-	backEnd.pc.c_dynamicVboDraws++;
+	backEnd.pc.c_dynamicVaoDraws++;
 
-	// update the default VBO
-	if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES)
+	// update the default VAO
+	if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES && tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES)
 	{
-		R_BindVBO(tess.vbo);
+		int attribIndex;
 
-		// orphan old buffer so we don't stall on it
-		qglBufferDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->vertexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
+		R_BindVao(tess.vao);
 
-		if(attribBits & ATTR_BITS)
+		// these may not be bound if we're using VAOs
+		if (glRefConfig.vertexArrayObject)
 		{
-			if(attribBits & ATTR_POSITION)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_xyz, tess.numVertexes * sizeof(tess.xyz[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz,         tess.numVertexes * sizeof(tess.xyz[0]),              tess.xyz);
-			}
+			qglBindBufferARB(GL_ARRAY_BUFFER_ARB, tess.vao->vertexesVBO);
+			qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tess.vao->indexesIBO);
+		}
 
-			if(attribBits & ATTR_TEXCOORD || attribBits & ATTR_LIGHTCOORD)
-			{
-				// these are interleaved, so we update both if either need it
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_st, tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2);
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st,          tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords);
-			}
+		// orphan old vertex buffer so we don't stall on it
+		qglBufferDataARB(GL_ARRAY_BUFFER_ARB, tess.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
 
-			if(attribBits & ATTR_NORMAL)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_normal, tess.numVertexes * sizeof(tess.normal[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal,      tess.numVertexes * sizeof(tess.normal[0]),           tess.normal);
-			}
+		// if nothing to set, set everything
+		if(!(attribBits & ATTR_BITS))
+			attribBits = ATTR_BITS;
 
-#ifdef USE_VERT_TANGENT_SPACE
-			if(attribBits & ATTR_TANGENT)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_tangent, tess.numVertexes * sizeof(tess.tangent[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent,     tess.numVertexes * sizeof(tess.tangent[0]),          tess.tangent);
-			}
-#endif
+		if(attribBits & ATTR_TEXCOORD || attribBits & ATTR_LIGHTCOORD)
+		{
+			// these are interleaved, so we update both if either need it
+			// this translates to updating ATTR_TEXCOORD twice as large as it needs
+			attribBits &= ~ATTR_LIGHTCOORD;
+			attribBits |= ATTR_TEXCOORD;
+		}
 
-			if(attribBits & ATTR_COLOR)
+		for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
+		{
+			if (attribBits & (1 << attribIndex))
 			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]),     tess.vertexColors);
-			}
+				vaoAttrib_t *vAtb = &tess.vao->attribs[attribIndex];
 
-			if(attribBits & ATTR_LIGHTDIRECTION)
-			{
-				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_lightdir, tess.numVertexes * sizeof(tess.lightdir[0]));
-				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir,    tess.numVertexes * sizeof(tess.lightdir[0]),         tess.lightdir);
+				// note: tess has a VBO where stride == size
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, vAtb->offset, tess.numVertexes * vAtb->stride, tess.attribPointers[attribIndex]);
 			}
 		}
-		else
-		{
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz,         tess.numVertexes * sizeof(tess.xyz[0]),              tess.xyz);
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st,          tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords);
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal,      tess.numVertexes * sizeof(tess.normal[0]),           tess.normal);
-#ifdef USE_VERT_TANGENT_SPACE
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent,     tess.numVertexes * sizeof(tess.tangent[0]),          tess.tangent);
-#endif
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]),     tess.vertexColors);
-			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir,    tess.numVertexes * sizeof(tess.lightdir[0]),         tess.lightdir);
-		}
-
-	}
-
-	// update the default IBO
-	if(tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES)
-	{
-		R_BindIBO(tess.ibo);
 
-		// orphan old buffer so we don't stall on it
-		qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tess.ibo->indexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
+		// orphan old index buffer so we don't stall on it
+		qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tess.vao->indexesSize, NULL, GL_DYNAMIC_DRAW_ARB);
 
 		qglBufferSubDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes);
 	}
diff --git a/SP/code/rend2/tr_world.c b/SP/code/rend2/tr_world.c
index 4987d83..b8910da 100644
--- a/SP/code/rend2/tr_world.c
+++ b/SP/code/rend2/tr_world.c
@@ -219,7 +219,7 @@ static int R_DlightSurface( msurface_t *surf, int dlightBits ) {
 		case SF_FACE:
 		case SF_GRID:
 		case SF_TRIANGLES:
-		case SF_VBO_MESH:
+		case SF_VAO_MESH:
 			((srfBspSurface_t *)surf->data)->dlightBits = dlightBits;
 			break;
 
@@ -305,7 +305,7 @@ static int R_PshadowSurface( msurface_t *surf, int pshadowBits ) {
 		case SF_FACE:
 		case SF_GRID:
 		case SF_TRIANGLES:
-		case SF_VBO_MESH:
+		case SF_VAO_MESH:
 			((srfBspSurface_t *)surf->data)->pshadowBits = pshadowBits;
 			break;
 
diff --git a/SP/code/renderer/qgl.h b/SP/code/renderer/qgl.h
index 1628a6b..cae6282 100644
--- a/SP/code/renderer/qgl.h
+++ b/SP/code/renderer/qgl.h
@@ -484,6 +484,16 @@ extern void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
 #define GL_TEXTURE_CUBE_MAP_SEAMLESS               0x884F
 #endif
 
+// GL_ARB_vertex_array_object
+extern void (APIENTRY * qglBindVertexArrayARB)(GLuint array);
+extern void (APIENTRY * qglDeleteVertexArraysARB)(GLsizei n, const GLuint *arrays);
+extern void (APIENTRY * qglGenVertexArraysARB)(GLsizei n, GLuint *arrays);
+extern GLboolean (APIENTRY * qglIsVertexArrayARB)(GLuint array);
+#ifndef GL_ARB_vertex_array_object
+#define GL_ARB_vertex_array_object
+#define GL_VERTEX_ARRAY_BINDING_ARB                0x85B5
+#endif
+
 #if defined(WIN32)
 // WGL_ARB_create_context
 #ifndef WGL_ARB_create_context
diff --git a/SP/rend2-readme.txt b/SP/rend2-readme.txt
index 1414b7c..f52d894 100644
--- a/SP/rend2-readme.txt
+++ b/SP/rend2-readme.txt
@@ -550,7 +550,7 @@ There are currently two ways to use this in your own (and other people's) maps.
         surfaceparm nolightmap
         surfaceparm sky
         q3map_sunExt 240 238 200 100 195 35 3 16
-        q3gl2_sun 240 238 200 50 195 35 3 1.0 0.2
+	q3gl2_sun 240 238 200 50 195 35 1.0 0.2
         q3map_skylight 50 16
         q3map_lightimage $whiteimage
 
@@ -573,7 +573,7 @@ There are currently two ways to use this in your own (and other people's) maps.
         surfaceparm noimpact
         surfaceparm nolightmap
         surfaceparm sky
-        q3gl2_sun 240 238 200 50 195 35 3 0.5 0.2
+	q3gl2_sun 240 238 200 50 195 35 0.5 0.2
         q3map_skylight 50 16
         q3map_lightimage $whiteimage
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/iortcw.git



More information about the Pkg-games-commits mailing list