Python Opengl Procedural Terrain Rendering

I am rendering procedural terrain in Python using OpenGL. Once generated, data for each terrain tile is stored in lists. However currently I am only getting around 25fps despite rendering only around 4000 polygons (2 per terrain tile). The model x and z coordinates are stored in the position matrix contained in the terrain_quad_location list where as the heights are stored in the vertices of each terrain tile quad in the terrain_VAO list.

It seems like this low fps is due to inefficiency when render large numbers of models not polygons as when I render models with high polygon counts in addition to this my fps is minimally affected. For reference I am using VAOs, VBOs and EBOs and built upon these tutorials.

Is there a way I can make the code more efficient?

Before Runtime:

count=0 for i in range(terrain_quad_number):      terrain_VAO[count] = glGenVertexArrays(1)     glBindVertexArray(terrain_VAO[count])      terrain_VBO[count] = glGenBuffers(1)     glBindBuffer(GL_ARRAY_BUFFER, terrain_VBO[count])     glBufferData(GL_ARRAY_BUFFER, terrain_quad_vertices[count].nbytes, terrain_quad_vertices[count], GL_STATIC_DRAW)       terrain_EBO[count] = glGenBuffers(1)     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, terrain_EBO[count])     glBufferData(GL_ELEMENT_ARRAY_BUFFER, terrain_quad_vertices[count].nbytes, quad_indices, GL_STATIC_DRAW)      glEnableVertexAttribArray(0)     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, terrain_quad_vertices[count].itemsize * 5, ctypes.c_void_p(0))      glEnableVertexAttribArray(1)     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, terrain_quad_vertices[count].itemsize * 5, ctypes.c_void_p(12)) 

Runtime:

moo=0     for i in range (len(rendered_terrain_number)):         model = terrain_quad_location[rendered_terrain_number[moo]]          glBindVertexArray(terrain_VAO[rendered_terrain_number[moo]])         glBindTexture(GL_TEXTURE_2D, ground_textures[int(terrain_quad_sub_biome[int(rendered_terrain_number[moo])][0][0])-1])         glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)         glDrawElements(GL_TRIANGLES, len(quad_indices), GL_UNSIGNED_INT, None)         moo=moo+1 

I understand that I may need to lower the number of draw calls which may be done my merging terrain tile models, but how do I do this given I want each tile to have its own texture?

Apparently Minecraft does this by merging every block in a chunk into one VBO but how is this done given every block has a different texture? Instancing would not work either.

Here is a video of the program.