1 /* 2 Copyright © 2020, Luna Nielsen 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module engine.render.texture; 8 public import engine.render.texture.packer; 9 public import engine.render.texture.atlas; 10 public import engine.render.texture.font; 11 import bindbc.opengl; 12 import std.exception; 13 import imagefmt; 14 import std.format; 15 import engine; 16 17 /** 18 Filtering mode for texture 19 */ 20 enum Filtering { 21 /** 22 Linear filtering will try to smooth out textures 23 */ 24 Linear = GL_LINEAR, 25 26 /** 27 Point filtering will try to preserve pixel edges. 28 Due to texture sampling being float based this is imprecise. 29 */ 30 Point = GL_POINT 31 } 32 33 /** 34 Texture wrapping modes 35 */ 36 enum Wrapping { 37 /** 38 Clamp texture sampling to be within the texture 39 */ 40 Clamp = GL_CLAMP_TO_BORDER, 41 42 /** 43 Wrap the texture in every direction idefinitely 44 */ 45 Repeat = GL_REPEAT, 46 47 /** 48 Wrap the texture mirrored in every direction indefinitely 49 */ 50 Mirror = GL_MIRRORED_REPEAT 51 } 52 53 /** 54 A texture which is not bound to an OpenGL context 55 56 Used for texture atlassing 57 */ 58 struct ShallowTexture { 59 public: 60 /** 61 8-bit RGBA color data 62 */ 63 ubyte[] data; 64 65 /** 66 Width of texture 67 */ 68 int width; 69 70 /** 71 Height of texture 72 */ 73 int height; 74 75 /** 76 Loads a shallow texture from image file 77 Supported file types: 78 * PNG 8-bit 79 * BMP 8-bit 80 * TGA 8-bit non-palleted 81 * JPEG baseline 82 */ 83 this(string file) { 84 85 // Load image from disk, as RGBA 8-bit 86 IFImage image = read_image(file, 4, 8); 87 enforce( image.e == 0, "%s: %s".format(IF_ERROR[image.e], file)); 88 scope(exit) image.free(); 89 90 // Copy data from IFImage to this ShallowTexture 91 this.data = new ubyte[image.buf8.length]; 92 this.data[] = image.buf8; 93 94 // Set the width/height data 95 this.width = image.w; 96 this.height = image.h; 97 } 98 } 99 100 /** 101 A texture, only format supported is unsigned 8 bit RGBA 102 */ 103 class Texture { 104 private: 105 GLuint id; 106 int width_; 107 int height_; 108 109 GLuint colorMode; 110 int alignment; 111 112 public: 113 114 /** 115 Loads texture from image file 116 Supported file types: 117 * PNG 8-bit 118 * BMP 8-bit 119 * TGA 8-bit non-palleted 120 * JPEG baseline 121 */ 122 this(string file) { 123 124 // Load image from disk, as RGBA 8-bit 125 IFImage image = read_image(file, 4, 8); 126 enforce( image.e == 0, "%s: %s".format(IF_ERROR[image.e], file)); 127 scope(exit) image.free(); 128 129 // Load in image data to OpenGL 130 this(image.buf8, image.w, image.h); 131 } 132 133 /** 134 Creates a texture from a ShallowTexture 135 */ 136 this(ShallowTexture shallow) { 137 this(shallow.data, shallow.width, shallow.height); 138 } 139 140 /** 141 Creates a new empty texture 142 */ 143 this(int width, int height, GLuint mode = GL_RGBA, int alignment = 4) { 144 145 // Create an empty texture array with no data 146 ubyte[] empty = new ubyte[width_*height_*alignment]; 147 148 // Pass it on to the other texturing 149 this(empty, width, height, mode, alignment); 150 } 151 152 /** 153 Creates a new texture from specified data 154 */ 155 this(ubyte[] data, int width, int height, GLuint mode = GL_RGBA, int alignment = 4) { 156 this.colorMode = mode; 157 this.alignment = alignment; 158 this.width_ = width; 159 this.height_ = height; 160 161 // Generate OpenGL texture 162 glGenTextures(1, &id); 163 this.setData(data); 164 165 // Set default filtering and wrapping 166 this.setFiltering(Filtering.Linear); 167 this.setWrapping(Wrapping.Clamp); 168 } 169 170 /** 171 Width of texture 172 */ 173 int width() { 174 return width_; 175 } 176 177 /** 178 Height of texture 179 */ 180 int height() { 181 return height_; 182 } 183 184 /** 185 Set the filtering mode used for the texture 186 */ 187 void setFiltering(Filtering filtering) { 188 this.bind(); 189 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 190 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 191 } 192 193 /** 194 Set the wrapping mode used for the texture 195 */ 196 void setWrapping(Wrapping wrapping) { 197 this.bind(); 198 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapping); 199 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapping); 200 } 201 202 /** 203 Sets the data of the texture 204 */ 205 void setData(ubyte[] data) { 206 this.bind(); 207 glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); 208 glTexImage2D(GL_TEXTURE_2D, 0, colorMode, width_, height_, 0, colorMode, GL_UNSIGNED_BYTE, data.ptr); 209 glGenerateMipmap(GL_TEXTURE_2D); 210 } 211 212 /** 213 Sets a region of a texture to new data 214 */ 215 void setDataRegion(ubyte[] data, int x, int y, int width, int height) { 216 this.bind(); 217 218 // Make sure we don't try to change the texture in an out of bounds area. 219 enforce( x >= 0 && x+width <= this.width_, "x offset is out of bounds (xoffset=%s, xbound=%s)".format(x+width, this.width_)); 220 enforce( y >= 0 && y+height <= this.height_, "y offset is out of bounds (yoffset=%s, ybound=%s)".format(y+height, this.height_)); 221 222 // Update the texture 223 glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); 224 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, this.colorMode, GL_UNSIGNED_BYTE, data.ptr); 225 glGenerateMipmap(GL_TEXTURE_2D); 226 } 227 228 /** 229 Bind this texture 230 231 Notes 232 - In release mode the unit value is clamped to 31 (The max OpenGL texture unit value) 233 - In debug mode unit values over 31 will assert. 234 */ 235 void bind(uint unit = 0) { 236 assert(unit <= 31u, "Outside maximum OpenGL texture unit value"); 237 glActiveTexture(GL_TEXTURE0+(unit <= 31u ? unit : 31u)); 238 glBindTexture(GL_TEXTURE_2D, id); 239 } 240 }