class ArrayTexture;
 class BlockTypeRegistry;
+class CubeMap;
 class Sound;
 class Texture;
 class TextureIndex;
        explicit AssetLoader(const std::string &base);
 
        void LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &, TextureIndex &) const;
+       CubeMap LoadCubeMap(const std::string &name) const;
        Font LoadFont(const std::string &name, int size) const;
        Sound LoadSound(const std::string &name) const;
        Texture LoadTexture(const std::string &name) const;
 
 #include "init.hpp"
 #include "../audio/Sound.hpp"
 #include "../graphics/ArrayTexture.hpp"
+#include "../graphics/CubeMap.hpp"
 #include "../graphics/Font.hpp"
 #include "../graphics/Texture.hpp"
 #include "../io/TokenStreamReader.hpp"
        }
 }
 
+CubeMap AssetLoader::LoadCubeMap(const string &name) const {
+       string full = textures + name;
+       string right = full + "-right.png";
+       string left = full + "-left.png";
+       string top = full + "-top.png";
+       string bottom = full + "-bottom.png";
+       string back = full + "-back.png";
+       string front = full + "-front.png";
+
+       CubeMap cm;
+       SDL_Surface *srf;
+
+       if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
+       cm.Data(CubeMap::RIGHT, *srf);
+       SDL_FreeSurface(srf);
+
+       if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
+       cm.Data(CubeMap::LEFT, *srf);
+       SDL_FreeSurface(srf);
+
+       if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
+       cm.Data(CubeMap::TOP, *srf);
+       SDL_FreeSurface(srf);
+
+       if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
+       cm.Data(CubeMap::BOTTOM, *srf);
+       SDL_FreeSurface(srf);
+
+       if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
+       cm.Data(CubeMap::BACK, *srf);
+       SDL_FreeSurface(srf);
+
+       if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
+       cm.Data(CubeMap::FRONT, *srf);
+       SDL_FreeSurface(srf);
+
+       return cm;
+}
+
 Font AssetLoader::LoadFont(const string &name, int size) const {
        string full = fonts + name + ".ttf";
        return Font(full.c_str(), size);
 
 #define BLANK_GRAPHICS_ARRAYTEXTURE_HPP_
 
 #include "Format.hpp"
+#include "TextureBase.hpp"
 
 #include <GL/glew.h>
 
 
 namespace blank {
 
-class ArrayTexture {
+class ArrayTexture
+: public TextureBase<GL_TEXTURE_2D_ARRAY> {
 
 public:
        ArrayTexture();
        GLsizei Height() const noexcept { return height; }
        GLsizei Depth() const noexcept { return depth; }
 
-       void Bind() noexcept;
-
        void Reserve(GLsizei w, GLsizei h, GLsizei d, const Format &) noexcept;
        void Data(GLsizei l, const SDL_Surface &);
        void Data(GLsizei l, const Format &, GLvoid *data) noexcept;
 
-       void FilterNearest() noexcept;
-       void FilterLinear() noexcept;
-       void FilterTrilinear() noexcept;
-
 private:
-       GLuint handle;
-
        GLsizei width, height, depth;
 
        Format format;
 
--- /dev/null
+#ifndef BLANK_GRAPHICS_CUBEMAP_HPP_
+#define BLANK_GRAPHICS_CUBEMAP_HPP_
+
+#include "Format.hpp"
+#include "TextureBase.hpp"
+
+#include <GL/glew.h>
+
+struct SDL_Surface;
+
+
+namespace blank {
+
+class CubeMap
+: public TextureBase<GL_TEXTURE_CUBE_MAP> {
+
+public:
+       enum Face {
+               RIGHT = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+               LEFT = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+               TOP = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+               BOTTOM = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+               BACK = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+               FRONT = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+       };
+
+public:
+       CubeMap();
+       ~CubeMap();
+
+       CubeMap(CubeMap &&) noexcept;
+       CubeMap &operator =(CubeMap &&) noexcept;
+
+       CubeMap(const CubeMap &) = delete;
+       CubeMap &operator =(const CubeMap &) = delete;
+
+public:
+       void Data(Face, const SDL_Surface &) noexcept;
+       void Data(Face, GLsizei w, GLsizei h, const Format &, GLvoid *data) noexcept;
+
+};
+
+}
+
+#endif
 
 
        SDL_PixelFormat sdl_format;
 
-       Format();
-       explicit Format(const SDL_PixelFormat &);
+       Format() noexcept;
+       explicit Format(const SDL_PixelFormat &) noexcept;
 
        bool Compatible(const Format &other) const noexcept;
 
 
 #ifndef BLANK_GRAPHICS_TEXTURE_HPP_
 #define BLANK_GRAPHICS_TEXTURE_HPP_
 
+#include "TextureBase.hpp"
+
 #include <GL/glew.h>
 
 struct SDL_Surface;
 
 struct Format;
 
-class Texture {
+class Texture
+: public TextureBase<GL_TEXTURE_2D> {
 
 public:
        Texture();
        GLsizei Width() const noexcept { return width; }
        GLsizei Height() const noexcept { return height; }
 
-       void Bind() noexcept;
-
        void Data(const SDL_Surface &, bool pad2 = true) noexcept;
        void Data(GLsizei w, GLsizei h, const Format &, GLvoid *data) noexcept;
 
-       void FilterNearest() noexcept;
-       void FilterLinear() noexcept;
-       void FilterTrilinear() noexcept;
-
        static void UnpackAlignment(GLint) noexcept;
        static int UnpackAlignmentFromPitch(int) noexcept;
        static void UnpackRowLength(GLint) noexcept;
 
 private:
-       GLuint handle;
-
        GLsizei width, height;
 
 };
 
--- /dev/null
+#ifndef BLANK_GRAPHICS_TEXTUREBASE_HPP_
+#define BLANK_GRAPHICS_TEXTUREBASE_HPP_
+
+#include <GL/glew.h>
+
+
+namespace blank {
+
+template<GLenum TARGET, GLsizei COUNT = 1>
+class TextureBase {
+
+public:
+       TextureBase();
+       ~TextureBase();
+
+       TextureBase(TextureBase &&other) noexcept;
+       TextureBase &operator =(TextureBase &&) noexcept;
+
+       TextureBase(const TextureBase &) = delete;
+       TextureBase &operator =(const TextureBase &) = delete;
+
+public:
+       void Bind(GLsizei which = 0) noexcept {
+               glBindTexture(TARGET, handle[which]);
+       }
+
+       void FilterNearest() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+               glTexParameteri(TARGET, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+       }
+       void FilterLinear() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+               glTexParameteri(TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       }
+       void FilterTrilinear() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+               glTexParameteri(TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+               glGenerateMipmap(TARGET);
+       }
+
+       void WrapEdge() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       }
+       void WrapBorder() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+       }
+       void WrapRepeat() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_REPEAT);
+       }
+       void WrapMirror() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
+       }
+
+private:
+       GLuint handle[COUNT];
+
+};
+
+}
+
+#endif
 
 #include "ArrayTexture.hpp"
+#include "CubeMap.hpp"
 #include "Font.hpp"
 #include "Format.hpp"
 #include "Texture.hpp"
+#include "TextureBase.hpp"
 #include "Viewport.hpp"
 
 #include "../app/init.hpp"
        SDL_FreeSurface(srf);
 }
 
-Format::Format()
+Format::Format() noexcept
 : format(GL_BGRA)
 , type(GL_UNSIGNED_INT_8_8_8_8_REV)
 , internal(GL_RGBA8) {
        sdl_format.next = nullptr;
 }
 
-Format::Format(const SDL_PixelFormat &fmt)
+Format::Format(const SDL_PixelFormat &fmt) noexcept
 : sdl_format(fmt) {
        if (fmt.BytesPerPixel == 4) {
                if (fmt.Amask == 0xFF) {
 }
 
 
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT>::TextureBase() {
+       glGenTextures(COUNT, handle);
+}
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT>::~TextureBase() {
+       glDeleteTextures(COUNT, handle);
+}
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT>::TextureBase(TextureBase &&other) noexcept {
+       std::memcpy(handle, other.handle, sizeof(handle));
+       std::memset(other.handle, 0, sizeof(handle));
+}
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT> &TextureBase<TARGET, COUNT>::operator =(TextureBase &&other) noexcept {
+       std::swap(handle, other.handle);
+       return *this;
+}
+
+
 Texture::Texture()
-: handle(0)
+: TextureBase()
 , width(0)
 , height(0) {
-       glGenTextures(1, &handle);
+
 }
 
 Texture::~Texture() {
-       if (handle != 0) {
-               glDeleteTextures(1, &handle);
-       }
+
 }
 
 Texture::Texture(Texture &&other) noexcept
-: handle(other.handle) {
-       other.handle = 0;
+: TextureBase(std::move(other)) {
        width = other.width;
        height = other.height;
 }
 
 Texture &Texture::operator =(Texture &&other) noexcept {
-       std::swap(handle, other.handle);
+       TextureBase::operator =(std::move(other));
        width = other.width;
        height = other.height;
        return *this;
 }
 
 
-void Texture::Bind() noexcept {
-       glBindTexture(GL_TEXTURE_2D, handle);
-}
-
 namespace {
        bool ispow2(unsigned int i) {
                // don't care about i == 0 here
 }
 
 
-void Texture::FilterNearest() noexcept {
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-}
-
-void Texture::FilterLinear() noexcept {
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-}
-
-void Texture::FilterTrilinear() noexcept {
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
-       glGenerateMipmap(GL_TEXTURE_2D);
-}
-
-
 void Texture::UnpackAlignment(GLint i) noexcept {
        glPixelStorei(GL_UNPACK_ALIGNMENT, i);
 }
 
 
 ArrayTexture::ArrayTexture()
-: handle(0)
+: TextureBase()
 , width(0)
 , height(0)
 , depth(0) {
-       glGenTextures(1, &handle);
+
 }
 
 ArrayTexture::~ArrayTexture() {
-       if (handle != 0) {
-               glDeleteTextures(1, &handle);
-       }
+
 }
 
 ArrayTexture::ArrayTexture(ArrayTexture &&other) noexcept
-: handle(other.handle) {
-       other.handle = 0;
+: TextureBase(std::move(other)) {
        width = other.width;
        height = other.height;
        depth = other.depth;
 }
 
 ArrayTexture &ArrayTexture::operator =(ArrayTexture &&other) noexcept {
-       std::swap(handle, other.handle);
+       TextureBase::operator =(std::move(other));
        width = other.width;
        height = other.height;
        depth = other.depth;
 }
 
 
-void ArrayTexture::Bind() noexcept {
-       glBindTexture(GL_TEXTURE_2D_ARRAY, handle);
-}
-
-
 void ArrayTexture::Reserve(GLsizei w, GLsizei h, GLsizei d, const Format &f) noexcept {
        glTexStorage3D(
                GL_TEXTURE_2D_ARRAY, // which
 }
 
 
-void ArrayTexture::FilterNearest() noexcept {
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+CubeMap::CubeMap()
+: TextureBase() {
+
 }
 
-void ArrayTexture::FilterLinear() noexcept {
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+CubeMap::~CubeMap() {
+
 }
 
-void ArrayTexture::FilterTrilinear() noexcept {
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-       glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
-       glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
+CubeMap::CubeMap(CubeMap &&other) noexcept
+: TextureBase(std::move(other)) {
+
+}
+
+CubeMap &CubeMap::operator =(CubeMap &&other) noexcept {
+       TextureBase::operator =(std::move(other));
+       return *this;
+}
+
+
+void CubeMap::Data(Face f, const SDL_Surface &srf) noexcept {
+       Data(f, srf.w, srf.h, Format(*srf.format), srf.pixels);
+}
+
+void CubeMap::Data(Face face, GLsizei w, GLsizei h, const Format &f, GLvoid *data) noexcept {
+       glTexImage2D(
+               face,             // which
+               0,                // mipmap level
+               f.internal,       // internal format
+               w, h,             // size
+               0,                // border
+               f.format, f.type, // pixel format
+               data              // pixel data
+       );
 }
 
 }