1 module glamour.texture; 2 3 private { 4 import glamour.gl : GLenum, GLuint, GLint, GLsizei, GL_UNSIGNED_BYTE, 5 glGenTextures, glBindTexture, glActiveTexture, 6 glTexImage1D, glTexImage2D, glTexParameteri, glTexParameterf, 7 glGetTexParameterfv, glDeleteTextures, GL_TEXTURE0, 8 GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, GL_PROXY_TEXTURE_2D_ARRAY, 9 GL_PROXY_TEXTURE_3D, GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, 10 GL_RGBA8, GL_RGB, GL_RGBA, glGenerateMipmap; 11 12 version(stb) { 13 import stb_image : stbi_load, stbi_image_free; 14 } else version(SDLImage2) { 15 import derelict.sdl2.sdl; 16 import derelict.sdl2.image; 17 version = _glad_sdl; 18 } else version(SDLImage) { 19 import derelict.sdl.sdl; 20 import derelict.sdl.image; 21 version = _glad_sdl; 22 } 23 24 import glamour.util : glenum2size, checkgl; 25 import std.traits : isPointer; 26 import std..string : toStringz; 27 import std.exception : enforce; 28 import std.conv : to; 29 30 debug import std.stdio : stderr; 31 } 32 33 34 /// This exception will be thrown if Texture2D.from_image fails to open the image. 35 class TextureException : Exception { 36 this(string msg) { 37 super(msg); 38 } 39 } 40 deprecated alias TextureException TextureError; 41 42 /// Every Texture-Class will mixin this template. 43 mixin template CommonTextureMethods() { 44 ~this() { 45 debug if(texture != 0) stderr.writefln("OpenGL: %s resources not released.", typeof(this).stringof); 46 } 47 48 /// Deletes the texture. 49 void remove() { 50 checkgl!glDeleteTextures(1, &texture); 51 texture = 0; 52 } 53 54 /// Sets a texture parameter. 55 void set_parameter(T)(GLuint name, T params) if(is(T : int) || is(T : float)) { 56 static if(is(T : int)) { 57 checkgl!glTexParameteri(target, name, params); 58 } else { 59 checkgl!glTexParameterf(target, name, params); 60 } 61 } 62 63 /// Queries a texture parameter from OpenGL. 64 float[] get_parameter(GLuint name) { 65 float[] ret; 66 67 if(name == GL_TEXTURE_BORDER_COLOR) { 68 ret.length = 4; 69 } else { 70 ret.length = 1; 71 } 72 73 checkgl!glGetTexParameterfv(target, name, ret.ptr); 74 return ret; 75 } 76 77 /// Generates mipmaps for the textre (also binds it) 78 void generate_mipmaps() { 79 bind(); 80 checkgl!glGenerateMipmap(target); 81 } 82 83 /// Binds the texture. 84 void bind() { 85 checkgl!glBindTexture(target, texture); 86 } 87 88 /// Activates the texture to $(I unit), passed to the function. 89 void activate(GLuint unit) { 90 checkgl!glActiveTexture(unit); 91 } 92 93 /// Activates the texture to $(B unit), the struct member. 94 void activate() { 95 activate(unit); 96 } 97 98 /// Binds the texture and activates it to $(I unit), passed to the function. 99 void bind_and_activate(GLuint unit) { 100 checkgl!glBindTexture(target, texture); 101 checkgl!glActiveTexture(unit); 102 } 103 104 /// Binds the texture and activates it to $(B unit), the struct member. 105 void bind_and_activate() { 106 bind_and_activate(unit); 107 } 108 109 /// Unbinds the texture. 110 void unbind() { 111 checkgl!glBindTexture(target, 0); 112 } 113 } 114 115 /// Interface every Texture implements. 116 interface ITexture { 117 GLuint get_unit(); /// 118 void remove(); /// 119 void set_parameter(T)(GLuint name, T params); /// 120 float[] get_parameter(GLuint name); /// 121 void set_data(T)(T data); /// 122 void generate_mipmaps(); /// 123 void bind(); /// 124 void activate(GLuint unit); /// 125 void activate(); /// 126 void bind_and_activate(GLuint unit); /// 127 void bind_and_activate(); /// 128 void unbind(); /// 129 } 130 131 132 /// Represents an OpenGL 1D texture. 133 /// The constructor must be used to avoid segmentation faults. 134 class Texture1D : ITexture { 135 static const GLenum target = GL_TEXTURE_1D; 136 137 /// The OpenGL texture name. 138 GLuint texture; 139 /// Alias this to texture. 140 alias texture this; 141 142 /// Holds the internal format passed to the constructor. 143 GLint internal_format; 144 /// Holds the format of the pixel data. 145 GLenum format; 146 /// Holds the OpenGL data type of the pixel data. 147 GLenum type; 148 /// Holds the texture unit. 149 GLuint unit; 150 GLuint get_unit() { return unit; } 151 152 /// 153 mixin CommonTextureMethods; 154 155 /// Generates the OpenGL texture and initializes the struct. 156 /// Params: 157 /// unit_ = Specifies the OpenGL texture uinit. 158 this(GLenum unit=GL_TEXTURE0) { 159 this.unit = unit; 160 161 checkgl!glGenTextures(1, &texture); 162 } 163 164 /// Sets the texture data. 165 /// Params: 166 /// data = A pointer to the image data or an array of the image data. 167 /// internal_format = Specifies the number of color components in the texture. 168 /// width = If data is an array and width is -1, the width will be infered from the array, otherwise it must be valid. 169 /// format = Specifies the format of the pixel data. 170 /// type = Specifies the data type of the pixel data. 171 /// 172 /// See_Also: 173 /// OpenGL, http://www.opengl.org/sdk/docs/man4/xhtml/glTexImage1D.xml 174 void set_data(T)(T data, GLint internal_format, int width, GLenum format, GLenum type) { 175 bind(); 176 177 this.internal_format = internal_format; 178 this.format = format; 179 this.type = type; 180 181 static if(isPointer!T) { 182 auto d = data; 183 } else { 184 auto d = data.ptr; 185 186 if(width == -1) { 187 width = cast(int)data.length; 188 } 189 } 190 191 checkgl!glTexImage1D(GL_TEXTURE_1D, 0, internal_format, width, 0, format, type, d); 192 } 193 } 194 195 /// Represents an OpenGL 2D texture. 196 /// The constructor must be used to avoid segmentation faults. 197 class Texture2D : ITexture { 198 static const GLenum target = GL_TEXTURE_2D; 199 200 /// The OpenGL texture name. 201 GLuint texture; 202 /// Alias this to texture. 203 alias texture this; 204 205 /// Holds the internal format passed to the constructor. 206 GLint internal_format; 207 /// Holds the texture width. 208 GLsizei width; 209 /// Holds the texture height. 210 GLsizei height; 211 /// Holds the format of the pixel data. 212 GLenum format; 213 /// Holds the OpenGL data type of the pixel data. 214 GLenum type; 215 /// Holds the texture unit. 216 GLenum unit; 217 GLuint get_unit() { return unit; } 218 /// If true (default) mipmaps will be generated with glGenerateMipmap. 219 bool mipmaps = true; 220 221 mixin CommonTextureMethods; 222 223 /// Generates the OpenGL texture and initializes the struct. 224 /// Params: 225 /// unit = Specifies the OpenGL texture uinit. 226 this(GLenum unit=GL_TEXTURE0) { 227 this.unit = unit; 228 229 checkgl!glGenTextures(1, &texture); 230 } 231 232 /// Sets the texture data. 233 /// Params: 234 /// data = A pointer to the image data or an array of the image data. 235 /// internal_format = Specifies the number of color components in the texture. 236 /// width = Specifies the width of the texture image. 237 /// height = Specifies the height of the texture image. 238 /// format = Specifies the format of the pixel data. 239 /// type = Specifies the data type of the pixel data. 240 /// mipmaps = Enables mipmap-generation. 241 /// level = Specifies the level-of-detail number. Level 0 is the base image level. Level n is the n th mipmap reduction image. 242 /// 243 /// See_Also: 244 /// OpenGL, http://www.opengl.org/sdk/docs/man4/xhtml/glTexImage2D.xml 245 /// 246 /// $(RED If mipmaps is true, the gl extensions must be loaded, otherwise bad things will happen!) 247 void set_data(T)(T data, GLint internal_format, GLsizei width, GLsizei height, 248 GLenum format, GLenum type, bool mipmaps=true, GLint level=0) { 249 bind(); 250 251 this.internal_format = internal_format; 252 this.width = width; 253 this.height = height; 254 this.format = format; 255 this.type = type; 256 this.mipmaps = mipmaps; 257 258 static if(isPointer!T) { 259 auto d = data; 260 } else { 261 auto d = data.ptr; 262 } 263 264 checkgl!glTexImage2D(GL_TEXTURE_2D, level, internal_format, width, height, 0, format, type, d); 265 266 if(mipmaps) { 267 checkgl!glGenerateMipmap(GL_TEXTURE_2D); 268 } 269 } 270 271 version(stb) { 272 /// Loads an image and afterwards loads it into a Texture2D struct. 273 /// Image can be loaded by stb and SDLImage. To select 274 /// specific way use versions stb, SDLImage2 (SDL2) or SDLImage (SDL1). 275 /// 276 /// $(RED SDLImage must be loaded and initialized manually!) 277 static Texture2D from_image(string filename) { 278 auto tex = new Texture2D(); 279 280 int x; 281 int y; 282 int comp; 283 ubyte* data = stbi_load(toStringz(filename), &x, &y, &comp, 0); 284 scope(exit) stbi_image_free(data); 285 286 if(data is null) { 287 throw new TextureException("Unable to load image: " ~ filename); 288 } 289 290 uint image_format; 291 switch(comp) { 292 case 3: image_format = GL_RGB; break; 293 case 4: image_format = GL_RGBA; break; 294 default: throw new TextureException("Unknown/Unsupported stbi image format"); 295 } 296 297 tex.set_data(data, image_format, x, y, image_format, GL_UNSIGNED_BYTE); 298 299 return tex; 300 } 301 } else version (_glad_sdl) { 302 static Texture2D from_image(string filename) { 303 auto tex = new Texture2D(); 304 305 // make sure the texture has the right side up 306 //thanks to tito http://stackoverflow.com/questions/5862097/sdl-opengl-screenshot-is-black 307 SDL_Surface* flip(SDL_Surface* surface) { 308 SDL_Surface* result = SDL_CreateRGBSurface(surface.flags, surface.w, surface.h, 309 surface.format.BytesPerPixel * 8, surface.format.Rmask, surface.format.Gmask, 310 surface.format.Bmask, surface.format.Amask); 311 312 ubyte* pixels = cast(ubyte*) surface.pixels; 313 ubyte* rpixels = cast(ubyte*) result.pixels; 314 uint pitch = surface.pitch; 315 uint pxlength = pitch * surface.h; 316 317 assert(result != null); 318 319 for(uint line = 0; line < surface.h; ++line) { 320 uint pos = line * pitch; 321 rpixels[pos..pos+pitch] = pixels[(pxlength-pos)-pitch..pxlength-pos]; 322 } 323 324 return result; 325 } 326 327 auto surface = IMG_Load(filename.toStringz()); 328 329 enforce(surface, new TextureException("Error loading image " ~ filename ~ ": " ~ to!string(SDL_GetError()))); 330 scope(exit) SDL_FreeSurface(surface); 331 332 enforce(surface.format.BytesPerPixel == 3 || surface.format.BytesPerPixel == 4, "With SDLImage Glamour supports loading images only with 3 or 4 bytes per pixel format."); 333 auto image_format = GL_RGB; 334 335 if (surface.format.BytesPerPixel == 4) { 336 image_format = GL_RGBA; 337 } 338 339 auto flipped = flip(surface); 340 tex.set_data(flipped.pixels, image_format, surface.w, surface.h, image_format, GL_UNSIGNED_BYTE); 341 SDL_FreeSurface(flipped); 342 343 return tex; 344 } 345 } 346 } 347 348 /// Base class, which represents an OpenGL 3D or 2D array texture. 349 /// The constructor must be used to avoid segmentation faults. 350 class Texture3DBase(GLenum target_) : ITexture { 351 static assert(target_ == GL_TEXTURE_3D || target_ == GL_PROXY_TEXTURE_3D || 352 target_ == GL_TEXTURE_2D_ARRAY || target_ == GL_PROXY_TEXTURE_2D_ARRAY); 353 354 static const GLenum target = target_; 355 356 /// The OpenGL texture name. 357 GLuint texture; 358 /// Alias this to texture. 359 alias texture this; 360 361 /// Holds the internal format passed to the constructor. 362 GLint internal_format; 363 /// Holds the format of the pixel data. 364 GLenum format; 365 /// Holds the OpenGL data type of the pixel data. 366 GLenum type; 367 /// Holds the texture unit. 368 GLuint unit; 369 GLuint get_unit() { return unit; } 370 371 /// 372 mixin CommonTextureMethods; 373 374 /// Generates the OpenGL texture and initializes the struct. 375 /// Params: 376 /// unit = Specifies the OpenGL texture uinit. 377 this(GLenum unit=GL_TEXTURE0) { 378 this.unit = unit; 379 380 checkgl!glGenTextures(1, &texture); 381 } 382 383 /// Sets the texture data. 384 /// Params: 385 /// data = A pointer to the image data or an array of the image data. 386 /// internal_format = Specifies the number of color components in the texture. 387 /// width = Specifies the width of the texture image. 388 /// height = Specifies the height of the texture image. 389 /// depth = Specifies the depth of the texture image, or the number of layers in a texture array. 390 /// format = Specifies the format of the pixel data. 391 /// type = Specifies the data type of the pixel data. 392 /// 393 /// See_Also: 394 /// http://www.opengl.org/sdk/docs/man4/xhtml/glTexImage3D.xml 395 void set_data(T)(T data, GLint internal_format, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLint level=0) { 396 bind(); 397 398 this.internal_format = internal_format; 399 this.width = width; 400 this.height = height; 401 this.depth = depth; 402 this.format = format; 403 this.type = type; 404 405 static if(isPointer!T) { 406 auto d = data; 407 } else { 408 auto d = data.ptr; 409 } 410 411 checkgl!glTexImage3D(target, level, internal_format, width, height, depth, 0, format, type, d); 412 } 413 } 414 415 alias Texture3DBase!(GL_TEXTURE_3D) Texture3D; 416 alias Texture3DBase!(GL_PROXY_TEXTURE_2D_ARRAY) Texture2DArray;