void afSetVertexAttributes(const InputElement elements[], int numElements, int numBuffers, GLuint const vertexBufferIds[], const int strides[])
{
	for (int i = 0; i < numElements; i++) {
		const InputElement& d = elements[i];
		glBindBuffer(GL_ARRAY_BUFFER, vertexBufferIds[d.inputSlot]);
		GLenum r = glGetError();
		if (r != GL_NO_ERROR) {
			aflog("glBindBuffer error! i=%d inputSlot=%d vbo=%d\n", i, d.inputSlot, vertexBufferIds[d.inputSlot].x);
		}
		afHandleGLError(glEnableVertexAttribArray(i));
		switch (d.format) {
		case SF_R32_FLOAT:
		case SF_R32G32_FLOAT:
		case SF_R32G32B32_FLOAT:
		case SF_R32G32B32A32_FLOAT:
			glVertexAttribPointer(i, d.format - SF_R32_FLOAT + 1, GL_FLOAT, GL_FALSE, strides[d.inputSlot], (void*)d.offset);
			break;
		case SF_R8_UNORM:
		case SF_R8G8_UNORM:
		case SF_R8G8B8_UNORM:
		case SF_R8G8B8A8_UNORM:
			glVertexAttribPointer(i, d.format - SF_R8_UNORM + 1, GL_UNSIGNED_BYTE, GL_TRUE, strides[d.inputSlot], (void*)d.offset);
			break;
		case SF_R8_UINT_TO_FLOAT:
		case SF_R8G8_UINT_TO_FLOAT:
		case SF_R8G8B8_UINT_TO_FLOAT:
		case SF_R8G8B8A8_UINT_TO_FLOAT:
			glVertexAttribPointer(i, d.format - SF_R8_UINT_TO_FLOAT + 1, GL_UNSIGNED_BYTE, GL_FALSE, strides[d.inputSlot], (void*)d.offset);
			break;
		case SF_R8_UINT:
		case SF_R8G8_UINT:
		case SF_R8G8B8_UINT:
		case SF_R8G8B8A8_UINT:
#ifdef AF_GLES31
			glVertexAttribIPointer(i, d.format - SF_R8_UINT + 1, GL_UNSIGNED_BYTE, strides[d.inputSlot], (void*)d.offset);
#endif
			break;
		case SF_R16_UINT_TO_FLOAT:
		case SF_R16G16_UINT_TO_FLOAT:
		case SF_R16G16B16_UINT_TO_FLOAT:
		case SF_R16G16B16A16_UINT_TO_FLOAT:
			glVertexAttribPointer(i, d.format - SF_R16_UINT_TO_FLOAT + 1, GL_UNSIGNED_SHORT, GL_FALSE, strides[d.inputSlot], (void*)d.offset);
			break;
		case SF_R16_UINT:
		case SF_R16G16_UINT:
		case SF_R16G16B16_UINT:
		case SF_R16G16B16A16_UINT:
#ifdef AF_GLES31
			glVertexAttribIPointer(i, d.format - SF_R16_UINT + 1, GL_UNSIGNED_SHORT, strides[d.inputSlot], (void*)d.offset);
#endif
			break;
		case SF_R32_UINT:
		case SF_R32G32_UINT:
		case SF_R32G32B32_UINT:
		case SF_R32G32B32A32_UINT:
#ifdef AF_GLES31
			glVertexAttribIPointer(i, d.format - SF_R32_UINT + 1, GL_UNSIGNED_INT, strides[d.inputSlot], (void*)d.offset);
#endif
			break;
		default:
			assert(0);
			break;
		}
#ifdef AF_GLES31
		if (d.perInstance) {
			glVertexAttribDivisor(i, 1);
		}
#endif
	}
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

GLuint afCreateVAO(const InputElement elements[], int numElements, int numBuffers, GLuint const vertexBufferIds[], const int strides[], GLuint ibo)
{
	VAOID vao;
	glGenVertexArrays(1, &vao.x);
	glBindVertexArray(vao);
	afSetVertexAttributes(elements, numElements, numBuffers, vertexBufferIds, strides);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
	glBindVertexArray(0);
	return vao;
}