1 /* 2 Copyright (c) 2017-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 Permission is hereby granted, free of charge, to any person or organization 6 obtaining a copy of the software and accompanying documentation covered by 7 this license (the "Software") to use, reproduce, display, distribute, 8 execute, and transmit the Software, and to prepare derivative works of the 9 Software, and to permit third-parties to whom the Software is furnished to 10 do so, all subject to the following: 11 12 The copyright notices in the Software and this entire statement, including 13 the above license grant, this restriction and the following disclaimer, 14 must be included in all copies of the Software, in whole or in part, and 15 all derivative works of the Software, unless such copies or derivative 16 works are solely in the form of machine-executable object code generated by 17 a source language processor. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 DEALINGS IN THE SOFTWARE. 26 */ 27 28 module dagon.graphics.texture; 29 30 import std.stdio; 31 import std.math; 32 import std.algorithm; 33 34 import dlib.core.memory; 35 import dlib.core.ownership; 36 import dlib.image.color; 37 import dlib.image.image; 38 import dlib.image.hdri; 39 import dlib.math.vector; 40 41 import dagon.core.bindings; 42 import dagon.core.application; 43 import dagon.graphics.containerimage; 44 45 // S3TC formats 46 enum GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; // DXT1/BC1_UNORM 47 enum GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; // DXT3/BC2_UNORM 48 enum GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; // DXT5/BC3_UNORM 49 50 // RGTC formats 51 enum GL_COMPRESSED_RED_RGTC1 = 0x8DBB; // BC4_UNORM 52 enum GL_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC; // BC4_SNORM 53 enum GL_COMPRESSED_RG_RGTC2 = 0x8DBD; // BC5_UNORM 54 enum GL_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE; // BC5_SNORM 55 56 // BPTC formats 57 enum GL_COMPRESSED_RGBA_BPTC_UNORM_ARB = 0x8E8C; // BC7_UNORM 58 enum GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB = 0x8E8D; // BC7_UNORM_SRGB 59 enum GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB = 0x8E8E; // BC6H_SF16 60 enum GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB = 0x8E8F; // BC6H_UF16 61 62 struct TextureFormat 63 { 64 GLenum format; 65 GLint internalFormat; 66 GLenum pixelType; 67 bool compressed; 68 uint blockSize; 69 uint mipLevels; 70 } 71 72 class Texture: Owner 73 { 74 SuperImage image; 75 76 GLuint tex; 77 GLenum format; 78 GLint intFormat; 79 GLenum type; 80 81 int width; 82 int height; 83 int numMipmapLevels; 84 85 Vector2f translation; 86 Vector2f scale; 87 float rotation; 88 89 bool useMipmapFiltering = true; 90 bool useLinearFiltering = true; 91 92 protected bool mipmapGenerated = false; 93 94 this(Owner owner) 95 { 96 super(owner); 97 translation = Vector2f(0.0f, 0.0f); 98 scale = Vector2f(1.0f, 1.0f); 99 rotation = 0.0f; 100 } 101 102 this(SuperImage img, Owner owner, bool genMipmaps = false) 103 { 104 super(owner); 105 translation = Vector2f(0.0f, 0.0f); 106 scale = Vector2f(1.0f, 1.0f); 107 rotation = 0.0f; 108 createFromImage(img, genMipmaps); 109 } 110 111 void createFromImage(SuperImage img, bool genMipmaps = true) 112 { 113 releaseGLTexture(); 114 115 image = img; 116 width = img.width; 117 height = img.height; 118 useMipmapFiltering = genMipmaps; 119 120 glGenTextures(1, &tex); 121 glActiveTexture(GL_TEXTURE0); 122 glBindTexture(GL_TEXTURE_2D, tex); 123 124 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 125 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 126 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 127 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 128 129 TextureFormat tf; 130 if (detectTextureFormat(img, tf)) 131 { 132 format = tf.format; 133 intFormat = tf.internalFormat; 134 type = tf.pixelType; 135 136 if (tf.compressed) 137 createCompressedTexture(img.data.ptr, tf.blockSize, tf.mipLevels); 138 else 139 createUncompressedTexture(img.data.ptr, genMipmaps); 140 } 141 else 142 { 143 writeln("Unsupported texture format"); 144 createFallbackTexture(); 145 } 146 147 glBindTexture(GL_TEXTURE_2D, 0); 148 } 149 150 protected void createUncompressedTexture(ubyte* data, bool genMipmaps) 151 { 152 glTexImage2D(GL_TEXTURE_2D, 0, intFormat, width, height, 0, format, type, cast(void*)data); 153 if (genMipmaps) 154 { 155 glGenerateMipmap(GL_TEXTURE_2D); 156 mipmapGenerated = true; 157 } 158 } 159 160 protected void createCompressedTexture(ubyte* data, uint blockSize, uint numMipMaps) 161 { 162 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 163 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, numMipMaps - 1); 164 165 uint w = width; 166 uint h = height; 167 uint offset = 0; 168 for (uint i = 0; i < numMipMaps; i++) 169 { 170 uint size = ((w + 3) / 4) * ((h + 3) / 4) * blockSize; 171 glCompressedTexImage2D(GL_TEXTURE_2D, i, intFormat, w, h, 0, size, cast(void*)(data + offset)); 172 offset += size; 173 w /= 2; 174 h /= 2; 175 } 176 177 mipmapGenerated = true; 178 } 179 180 protected void createFallbackTexture() 181 { 182 // TODO: make fallback texture 183 } 184 185 void enableRepeat(bool mode) 186 { 187 if (glIsTexture(tex)) 188 { 189 glBindTexture(GL_TEXTURE_2D, tex); 190 191 if (mode) 192 { 193 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 194 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 195 } 196 else 197 { 198 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 199 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 200 } 201 } 202 } 203 204 void bind() 205 { 206 if (glIsTexture(tex)) 207 { 208 glBindTexture(GL_TEXTURE_2D, tex); 209 210 if (!mipmapGenerated && useMipmapFiltering) 211 { 212 glGenerateMipmap(GL_TEXTURE_2D); 213 mipmapGenerated = true; 214 } 215 216 if (useMipmapFiltering) 217 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 218 else if (useLinearFiltering) 219 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 220 else 221 { 222 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 223 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 224 } 225 } 226 } 227 228 void unbind() 229 { 230 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 231 glBindTexture(GL_TEXTURE_2D, 0); 232 } 233 234 bool valid() 235 { 236 return cast(bool)glIsTexture(tex); 237 } 238 239 Color4f sample(float u, float v) 240 { 241 if (image) 242 { 243 int x = cast(int)floor(u * width); 244 int y = cast(int)floor(v * height); 245 return image[x, y]; 246 } 247 else 248 return Color4f(0, 0, 0, 0); 249 } 250 251 void release() 252 { 253 releaseGLTexture(); 254 if (image) 255 { 256 Delete(image); 257 image = null; 258 } 259 } 260 261 void releaseGLTexture() 262 { 263 if (glIsTexture(tex)) 264 glDeleteTextures(1, &tex); 265 } 266 267 ~this() 268 { 269 release(); 270 } 271 } 272 273 bool detectTextureFormat(SuperImage img, out TextureFormat tf) 274 { 275 ContainerImage compressedImg = cast(ContainerImage)img; 276 if (compressedImg) 277 { 278 uint compFormat = compressedImg.pixelFormat; 279 switch(compFormat) 280 { 281 case ContainerImageFormat.R8: tf.internalFormat = GL_R8; tf.format = GL_RED; tf.pixelType = GL_UNSIGNED_BYTE; tf.blockSize = 0; tf.compressed = false; break; 282 case ContainerImageFormat.RG8: tf.internalFormat = GL_RG8; tf.format = GL_RG; tf.pixelType = GL_UNSIGNED_BYTE; tf.blockSize = 0; tf.compressed = false; break; 283 case ContainerImageFormat.RGB8: tf.internalFormat = GL_RGB8; tf.format = GL_RGB; tf.pixelType = GL_UNSIGNED_BYTE; tf.blockSize = 0; tf.compressed = false; break; 284 case ContainerImageFormat.RGBA8: tf.internalFormat = GL_RGBA8; tf.format = GL_RGBA; tf.pixelType = GL_UNSIGNED_BYTE; tf.blockSize = 0; tf.compressed = false; break; 285 case ContainerImageFormat.RGBAF32: tf.internalFormat = GL_RGBA32F; tf.format = GL_RGBA; tf.pixelType = GL_FLOAT; tf.blockSize = 0; tf.compressed = false; break; 286 case ContainerImageFormat.RGBAF16: tf.internalFormat = GL_RGBA16F; tf.format = GL_RGBA; tf.pixelType = GL_HALF_FLOAT; tf.blockSize = 0; tf.compressed = false; break; 287 case ContainerImageFormat.S3TC_RGB_DXT1: tf.internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; tf.blockSize = 8; tf.compressed = true; break; 288 case ContainerImageFormat.S3TC_RGBA_DXT3: tf.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; tf.blockSize = 16; tf.compressed = true; break; 289 case ContainerImageFormat.S3TC_RGBA_DXT5: tf.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; tf.blockSize = 16; tf.compressed = true; break; 290 case ContainerImageFormat.BPTC_RGBA_UNORM: tf.internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; tf.blockSize = 16; tf.compressed = true; break; 291 case ContainerImageFormat.BPTC_SRGBA_UNORM: tf.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; tf.blockSize = 16; tf.compressed = true; break; 292 default: 293 return false; 294 } 295 tf.mipLevels = compressedImg.mipLevels; 296 } 297 else 298 { 299 uint pixelFormat = img.pixelFormat; 300 switch(pixelFormat) 301 { 302 case IntegerPixelFormat.L8: tf.internalFormat = GL_R8; tf.format = GL_RED; tf.pixelType = GL_UNSIGNED_BYTE; break; 303 case IntegerPixelFormat.LA8: tf.internalFormat = GL_RG8; tf.format = GL_RG; tf.pixelType = GL_UNSIGNED_BYTE; break; 304 case IntegerPixelFormat.RGB8: tf.internalFormat = GL_RGB8; tf.format = GL_RGB; tf.pixelType = GL_UNSIGNED_BYTE; break; 305 case IntegerPixelFormat.RGBA8: tf.internalFormat = GL_RGBA8; tf.format = GL_RGBA; tf.pixelType = GL_UNSIGNED_BYTE; break; 306 case FloatPixelFormat.RGBAF32: tf.internalFormat = GL_RGBA32F; tf.format = GL_RGBA; tf.pixelType = GL_FLOAT; break; 307 default: 308 return false; 309 } 310 tf.compressed = false; 311 tf.blockSize = 0; 312 tf.mipLevels = 1; 313 } 314 315 return true; 316 }