What user1118321 says is true -- all the textures need to be the same size.
But that doesn't mean you need to USE all the space. For a small set of standalone textures, it's not a big deal to waste some space.
You can simply store the U/V coordinates of where a smaller texture ends and use that. When creating the array, just set a max size and make sure everything is <= that.
Here's some code from my project that I use to do this -- it uses some data structures not presented here, so you'll have to modify it a bit, but the idea should help. This works in 3.3 + a common extension or two (I forget which ones) or 4.3 (4.4?) without any (I think).
// this takes a vector containing the raw image data from the files (I use lodepng) as well as some sizing data also returned from lodepng
// max width/height are the actual size of the textures to be created
GLuint Graphics::make_texture_array(std::vector<ArrayTextureData> &
textures, size_t max_width, size_t max_height, bool repeatable) {
GLuint texture_name;
glGenTextures(1, &texture_name);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_name);
std::vector<u_int32_t> clear_data(max_width * max_height, 0);
// printf("Creating texture array with %d textures and max texture width %d and max texture height %d\n", (int)textures.size(), max_width, max_height);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, max_width, max_height, textures.size());
for (int i = 0; i < textures.size(); i++) {
auto & texture = textures[i];
// printf("Loading texture into array at position %d actual texture w: %d actual texture h: %d with %d bytes\n", i, texture.width, texture.height, (int)texture.data.size());
assert(texture.width <= max_width);
assert(texture.height <= max_height);
// set the whole texture to transparent (so min/mag filters don't find bad data off the edge of the actual image data)
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, max_width, max_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, &clear_data[0]);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, texture.width, texture.height, 1, GL_RGBA, GL_UNSIGNED_BYTE, &texture.data[0]);
CHECK_GL_ERROR;
texture.min_u = 0;
texture.min_v = 0;
texture.layer = i;
texture.max_u = texture.width / static_cast<float>(max_width);
texture.max_v = texture.height / static_cast<float>(max_height);
texture.texture_id = texture_name;
}
if (repeatable) {
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
} else {
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);
}
// makes enlarged textures run faster but look awful
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
CHECK_GL_ERROR;
//Unbind texture
glBindTexture( GL_TEXTURE_2D_ARRAY, NULL);
return texture_name;
}