Array Textures

Another useful extension which, in fact, as been in OpenGL since version 3.0 is Array Textures. Array textures allows to batch across texture changes by packing multiple textures into arrays. This way you can batch together objects using different textures reducing the number of state changes and draw calls ( more on the later in the next article about MultiDrawIndirect ). Basically, using array textures, you can load up several 1D, 2D or cubemap images into a single texture object. As the name implies, this is an array of textures, and, just like in C/C++, all elements in the array have to be of the same type. In this case, this means all textures loaded into an array texture have to use the same format and the same size.
To create a 2D array texture, simply create a new texture object bound to GL_TEXTURE_2D_ARRAY target, allocate storage for it using glTexStorage3D() and then load the different images ( or layers ) using glTexSubImage3D(). This program shows a trivial example where multiple quads using each one a different texture are rendered with a single array texture. This is how the output looks like

ArrayTexture

And this is the code. The interesting bits are in the function named GenerateArrayTexture()

#include <iostream> //std::cerr
#include <cstdlib>  //rand
 
#include "GL/glew.h"
#include "GL/freeglut.h"
 
namespace
{
  struct SVertex2D
  {
    float x,y;  //Position
    float u,v;  //Uv
  };
 
  const GLchar* gVertexShaderSource[] = {
                  "#version 430 core\n"
                  "layout (location = 0 ) in vec2 position;\n"
                  "layout (location = 1 ) in vec2 texCoord;\n"
                  "out vec2 uv;\n"
                  "void main(void)\n"
                  "{\n"
                  "  gl_Position = vec4(position,0.0,1.0);\n"
                  "  uv = texCoord;\n"
                  "}\n"
                   };
 
  const GLchar* gFragmentShaderSource[] = {
                  "#version 430 core\n"
                  "out vec4 color;\n"
                  "in vec2 uv;\n"
                  "layout (binding=0) uniform sampler2DArray textureArray;\n"
                  "layout (location=1) uniform int layer;\n"
                  "void main(void)\n"
                  "{\n"
                  "  color = texture(textureArray, vec3(uv.x,uv.y,layer) );\n"
                  "}\n"
                   };
 
  const SVertex2D gQuad[] = { {0.0f,0.0f,0.0f,0.0f},
                                                   {0.1f,0.0f,1.0f,0.0f},
                                                   {0.0f,0.1f,0.0f,1.0f},
                                                   {0.1f,0.1f,1.0f,1.0f}
                                    };
 
  const unsigned int gIndex[] = {0,1,2,1,3,2};
 
  GLuint gArrayTexture(0);
  GLuint gVertexBuffer(0);
  GLuint gElementBuffer(0);
  GLuint gProgram(0);
 
}//Unnamed namespace
 
void GenerateGeometry()
{
  //Generate 100 little quads
  SVertex2D vVertex[400];
  int index(0);
  float xOffset(-0.95f);
  float yOffset(-0.95f );
  for( unsigned int i(0); i!=10; ++i )
  {
    for( unsigned int j(0); j!=10; ++j )
    {
        for( unsigned int k(0); k!=4; ++k)
       {
         vVertex[index].x = gQuad[k].x+xOffset;
         vVertex[index].y = gQuad[k].y+yOffset;
         vVertex[index].u = gQuad[k].u;
         vVertex[index].v = gQuad[k].v;
	 index++;
      } 
      xOffset += 0.2f;
    }
    yOffset += 0.2f;
    xOffset = -0.95f;
  }
 
  //Create a vertex buffer object
  glGenBuffers( 1, &gVertexBuffer );
  glBindBuffer( GL_ARRAY_BUFFER, gVertexBuffer );
  glBufferData( GL_ARRAY_BUFFER, sizeof(vVertex), vVertex, GL_STATIC_DRAW );
 
  //Specify vertex attributes for the shader
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex2D), (GLvoid*)0 );
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex2D), (GLvoid*)8 );
 
  //Create an element buffer
  glGenBuffers( 1, &gElementBuffer );
  glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, gElementBuffer );
  glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(gIndex), gIndex, GL_STATIC_DRAW );
}
 
void GenerateArrayTexture()
{
  //Generate an array texture
  glGenTextures( 1, &gArrayTexture );
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D_ARRAY, gArrayTexture);
 
  //Create storage for the texture. (100 layers of 1x1 texels)
  glTexStorage3D( GL_TEXTURE_2D_ARRAY,
                  1,                    //No mipmaps as textures are 1x1
                  GL_RGB8,              //Internal format
                  1, 1,                 //width,height
                  100                   //Number of layers
                );
 
  for( unsigned int i(0); i!=100;++i)
  {
    //Choose a random color for the i-essim image
    GLubyte color[3] = {rand()%255,rand()%255,rand()%255};
 
    //Specify i-essim image
    glTexSubImage3D( GL_TEXTURE_2D_ARRAY,
                     0,                     //Mipmap number
                     0,0,i,                 //xoffset, yoffset, zoffset
                     1,1,1,                 //width, height, depth
                     GL_RGB,                //format
                     GL_UNSIGNED_BYTE,      //type
                     color);                //pointer to data
  }
 
  glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
}
 
GLuint CompileShaders(const GLchar** vertexShaderSource, const GLchar** fragmentShaderSource )
{
  //Compile vertex shader
  GLuint vertexShader( glCreateShader( GL_VERTEX_SHADER ) );
  glShaderSource( vertexShader, 1, vertexShaderSource, NULL );
  glCompileShader( vertexShader );
 
  //Compile fragment shader
  GLuint fragmentShader( glCreateShader( GL_FRAGMENT_SHADER ) );
  glShaderSource( fragmentShader, 1, fragmentShaderSource, NULL );
  glCompileShader( fragmentShader );
 
  //Link vertex and fragment shader together
  GLuint program( glCreateProgram() );
  glAttachShader( program, vertexShader );
  glAttachShader( program, fragmentShader );
  glLinkProgram( program );
 
  //Delete shaders objects
  glDeleteShader( vertexShader );
  glDeleteShader( fragmentShader );
 
  return program;
}
 
void Init(void)
{
  //Check if Opengl version is at least 3.0
  const GLubyte* glVersion( glGetString(GL_VERSION) );
  int major = glVersion[0] - '0';
  int minor = glVersion[2] - '0';
  if( major < 3 || minor < 0 )
  {
    std::cerr<<"ERROR: Minimum OpenGL version required for this demo is 3.0. Your current version is "<<major<<"."<<minor<<std::endl;
    exit(-1);
  }
 
  //Init glew
  glewInit();
 
  //Set clear color
  glClearColor(1.0, 1.0, 1.0, 0.0);
 
  //Create and bind the shader program
  gProgram = CompileShaders( gVertexShaderSource, gFragmentShaderSource );
  glUseProgram(gProgram);
 
  glUniform1i(0,0); //Sampler refers to texture unit 0
  
  GenerateGeometry();
  GenerateArrayTexture();
}
 
void Display()
{
  glClear( GL_COLOR_BUFFER_BIT );
  for( GLint i(0); i!=100; ++i )
  {
     glUniform1i(1, i);
     glDrawElementsBaseVertex( GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, i*4 );
  }
  glutSwapBuffers();
}
 
void Quit()
{
  //Clean-up
  glDeleteProgram(gProgram);
  glDeleteBuffers( 1, &gVertexBuffer );
  glDeleteBuffers( 1, &gElementBuffer );
  glDeleteTextures( 1, &gArrayTexture );
 
  //Exit application
  exit(0);
}
 
void OnKeyPress( unsigned char key, int x, int y )
{
  //'Esc' key
  if( key == 27 )
    Quit();
}
 
int main( int argc, char** argv )
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
  glutInitWindowSize(400,400);
  glutCreateWindow("Array Texture Example");
  glutIdleFunc(Display);
  glutKeyboardFunc( OnKeyPress );
 
  Init();
 
  //Enter the GLUT event loop
  glutMainLoop();
}
Advertisements

5 thoughts on “Array Textures

  1. I was a bit disappointed to see the 100 draw calls at the end; surely you could have drawn all quads in one batch, using something like “gl_VertexID % 4” to determine the layer index?

    • Sure, you can render everything in a single draw call using instanciation (or the trick you have suggested by the way) but that was not the point of the article. I tried to keep things as simple as possible to illustrate the usage of Array textures so people doesn’t have to figure out what the rest of the code or the shader were doing.

  2. Pingback: OpenGL API Overhead | Love.Lift.Levelup

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s