1 /* 2 Copyright (c) 2017-2023 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 module dagon.graphics.texture; 28 29 import std.stdio; 30 import std.math; 31 import std.algorithm; 32 import std.traits; 33 34 import dlib.core.memory; 35 import dlib.core.ownership; 36 import dlib.container.array; 37 import dlib.image.image; 38 import dlib.image.color; 39 import dlib.image.hdri; 40 import dlib.image.unmanaged; 41 import dlib.math.utils; 42 import dlib.math.vector; 43 import dlib.math.matrix; 44 import dlib.math.transformation; 45 46 import dagon.core.bindings; 47 48 // S3TC formats 49 enum GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; // DXT1/BC1_UNORM 50 enum GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; // DXT3/BC2_UNORM 51 enum GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; // DXT5/BC3_UNORM 52 53 // RGTC formats 54 /* 55 enum GL_COMPRESSED_RED_RGTC1 = 0x8DBB; // BC4_UNORM 56 enum GL_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC; // BC4_SNORM 57 enum GL_COMPRESSED_RG_RGTC2 = 0x8DBD; // BC5_UNORM 58 enum GL_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE; // BC5_SNORM 59 */ 60 61 // BPTC formats 62 enum GL_COMPRESSED_RGBA_BPTC_UNORM_ARB = 0x8E8C; // BC7_UNORM 63 enum GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB = 0x8E8D; // BC7_UNORM_SRGB 64 enum GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB = 0x8E8E; // BC6H_SF16 65 enum GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB = 0x8E8F; // BC6H_UF16 66 67 // ASTC formats 68 enum GL_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0; 69 enum GL_COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1; 70 enum GL_COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2; 71 enum GL_COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3; 72 enum GL_COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4; 73 enum GL_COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5; 74 enum GL_COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6; 75 enum GL_COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7; 76 enum GL_COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8; 77 enum GL_COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9; 78 enum GL_COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA; 79 enum GL_COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB; 80 enum GL_COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC; 81 enum GL_COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD; 82 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0; 83 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1; 84 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2; 85 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3; 86 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4; 87 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5; 88 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6; 89 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7; 90 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8; 91 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9; 92 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA; 93 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB; 94 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC; 95 enum GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD; 96 97 enum TextureDimension 98 { 99 Undefined, 100 D1, 101 D2, 102 D3 103 } 104 105 struct TextureSize 106 { 107 uint width; 108 uint height; 109 uint depth; 110 } 111 112 enum CubeFace: GLenum 113 { 114 PositiveX = GL_TEXTURE_CUBE_MAP_POSITIVE_X, 115 NegativeX = GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 116 PositiveY = GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 117 NegativeY = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 118 PositiveZ = GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 119 NegativeZ = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 120 } 121 122 enum CubeFaceBit 123 { 124 None = 0, 125 PositiveX = 1, 126 NegativeX = 2, 127 PositiveY = 4, 128 NegativeY = 8, 129 PositiveZ = 16, 130 NegativeZ = 32, 131 All = 0xffffffff 132 } 133 134 CubeFaceBit cubeFaceBit(CubeFace face) 135 { 136 CubeFaceBit cfb = CubeFaceBit.None; 137 switch(face) 138 { 139 case CubeFace.PositiveX: cfb = CubeFaceBit.PositiveX; break; 140 case CubeFace.NegativeX: cfb = CubeFaceBit.NegativeX; break; 141 case CubeFace.PositiveY: cfb = CubeFaceBit.PositiveY; break; 142 case CubeFace.NegativeY: cfb = CubeFaceBit.NegativeY; break; 143 case CubeFace.PositiveZ: cfb = CubeFaceBit.PositiveZ; break; 144 case CubeFace.NegativeZ: cfb = CubeFaceBit.NegativeZ; break; 145 default: break; 146 } 147 return cfb; 148 } 149 150 struct TextureFormat 151 { 152 GLenum target; 153 GLenum format; 154 GLint internalFormat; 155 GLenum pixelType; 156 uint blockSize; 157 uint cubeFaces; // bitwise combination of CubeFaceBit members 158 } 159 160 enum uint[GLenum] numChannelsFormat = [ 161 // Uncompressed formats 162 GL_RED: 1, 163 GL_RG: 2, 164 GL_RGB: 3, 165 GL_BGR: 3, 166 GL_RGBA: 4, 167 GL_BGRA: 4, 168 GL_RED_INTEGER: 1, 169 GL_RG_INTEGER: 2, 170 GL_RGB_INTEGER: 3, 171 GL_BGR_INTEGER: 3, 172 GL_RGBA_INTEGER: 4, 173 GL_BGRA_INTEGER: 4, 174 GL_STENCIL_INDEX: 1, 175 GL_DEPTH_COMPONENT: 1, 176 GL_DEPTH_STENCIL: 1, 177 178 // Compressed formats 179 GL_COMPRESSED_RED: 1, 180 GL_COMPRESSED_RG: 2, 181 GL_COMPRESSED_RGB: 3, 182 GL_COMPRESSED_RGBA: 4, 183 GL_COMPRESSED_SRGB: 3, 184 GL_COMPRESSED_SRGB_ALPHA: 4, 185 GL_COMPRESSED_RED_RGTC1: 1, 186 GL_COMPRESSED_SIGNED_RED_RGTC1: 1, 187 GL_COMPRESSED_RG_RGTC2: 2, 188 GL_COMPRESSED_SIGNED_RG_RGTC2: 2, 189 GL_COMPRESSED_RGB_S3TC_DXT1_EXT: 3, 190 GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: 4, 191 GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: 4, 192 GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: 4, 193 GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: 3, 194 GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB: 3, 195 GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: 3, 196 GL_COMPRESSED_RGBA_ASTC_4x4_KHR: 4, 197 GL_COMPRESSED_RGBA_ASTC_5x4_KHR: 4, 198 GL_COMPRESSED_RGBA_ASTC_5x5_KHR: 4, 199 GL_COMPRESSED_RGBA_ASTC_6x5_KHR: 4, 200 GL_COMPRESSED_RGBA_ASTC_6x6_KHR: 4, 201 GL_COMPRESSED_RGBA_ASTC_8x5_KHR: 4, 202 GL_COMPRESSED_RGBA_ASTC_8x6_KHR: 4, 203 GL_COMPRESSED_RGBA_ASTC_8x8_KHR: 4, 204 GL_COMPRESSED_RGBA_ASTC_10x5_KHR: 4, 205 GL_COMPRESSED_RGBA_ASTC_10x6_KHR: 4, 206 GL_COMPRESSED_RGBA_ASTC_10x8_KHR: 4, 207 GL_COMPRESSED_RGBA_ASTC_10x10_KHR: 4, 208 GL_COMPRESSED_RGBA_ASTC_12x10_KHR: 4, 209 GL_COMPRESSED_RGBA_ASTC_12x12_KHR: 4, 210 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: 4, 211 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR: 4, 212 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR: 4, 213 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR: 4, 214 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR: 4, 215 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR: 4, 216 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR: 4, 217 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR: 4, 218 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR: 4, 219 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR: 4, 220 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR: 4, 221 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR: 4, 222 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR: 4, 223 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR: 4 224 ]; 225 226 enum GLint[] compressedFormats = [ 227 GL_COMPRESSED_RED, 228 GL_COMPRESSED_RG, 229 GL_COMPRESSED_RGB, 230 GL_COMPRESSED_RGBA, 231 GL_COMPRESSED_SRGB, 232 GL_COMPRESSED_SRGB_ALPHA, 233 GL_COMPRESSED_RED_RGTC1, 234 GL_COMPRESSED_SIGNED_RED_RGTC1, 235 GL_COMPRESSED_RG_RGTC2, 236 GL_COMPRESSED_SIGNED_RG_RGTC2, 237 GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 238 GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 239 GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 240 GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, 241 GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, 242 GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, 243 GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, 244 GL_COMPRESSED_RGBA_ASTC_4x4_KHR, 245 GL_COMPRESSED_RGBA_ASTC_5x4_KHR, 246 GL_COMPRESSED_RGBA_ASTC_5x5_KHR, 247 GL_COMPRESSED_RGBA_ASTC_6x5_KHR, 248 GL_COMPRESSED_RGBA_ASTC_6x6_KHR, 249 GL_COMPRESSED_RGBA_ASTC_8x5_KHR, 250 GL_COMPRESSED_RGBA_ASTC_8x6_KHR, 251 GL_COMPRESSED_RGBA_ASTC_8x8_KHR, 252 GL_COMPRESSED_RGBA_ASTC_10x5_KHR, 253 GL_COMPRESSED_RGBA_ASTC_10x6_KHR, 254 GL_COMPRESSED_RGBA_ASTC_10x8_KHR, 255 GL_COMPRESSED_RGBA_ASTC_10x10_KHR, 256 GL_COMPRESSED_RGBA_ASTC_12x10_KHR, 257 GL_COMPRESSED_RGBA_ASTC_12x12_KHR, 258 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, 259 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, 260 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, 261 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, 262 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, 263 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, 264 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, 265 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, 266 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, 267 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, 268 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, 269 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, 270 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, 271 GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 272 ]; 273 274 struct TextureBuffer 275 { 276 TextureFormat format; 277 TextureSize size; 278 uint mipLevels; 279 ubyte[] data; 280 } 281 282 class Texture: Owner 283 { 284 GLuint texture; 285 TextureFormat format; 286 TextureSize size; 287 bool generateMipmaps; 288 uint mipLevels; 289 GLint minFilter = GL_LINEAR; 290 GLint magFilter = GL_LINEAR; 291 GLint wrapS = GL_REPEAT; 292 GLint wrapT = GL_REPEAT; 293 GLint wrapR = GL_REPEAT; 294 295 this(Owner owner) 296 { 297 super(owner); 298 } 299 300 ~this() 301 { 302 release(); 303 } 304 305 void createBlank(uint w, uint h, uint channels, uint bitDepth, bool genMipmaps, Color4f fillColor = Color4f(0.0f, 0.0f, 0.0f, 1.0f)) 306 { 307 release(); 308 309 SuperImage img = unmanagedImage(w, h, channels, bitDepth); 310 311 foreach(y; 0..img.height) 312 foreach(x; 0..img.width) 313 { 314 img[x, y] = fillColor; 315 } 316 317 createFromImage(img, genMipmaps); 318 319 Delete(img); 320 } 321 322 void createFromImage(SuperImage img, bool genMipmaps) 323 { 324 release(); 325 326 this.generateMipmaps = genMipmaps; 327 328 if (detectTextureFormat(img, this.format)) 329 { 330 this.size = TextureSize(img.width, img.height, 1); 331 this.mipLevels = 1; 332 createTexture2D(img.data); 333 } 334 else 335 { 336 writeln("Unsupported image format ", img.pixelFormat); 337 createFallbackTexture(); 338 } 339 } 340 341 void createFromImage3D(SuperImage img, uint size = 0) 342 { 343 if (size == 0) 344 { 345 size = cast(uint)cbrt(img.width * img.height); 346 } 347 else 348 { 349 if (img.width != img.height || img.width * img.height != size * size * size) 350 { 351 uint s = cast(uint)sqrt(cast(real)size * size * size); 352 writeln("Wrong image resolution for 3D texture size ", size, ": should be ", s, "x", s); 353 return; 354 } 355 } 356 357 TextureFormat format; 358 detectTextureFormat(img, format); 359 TextureBuffer buff; 360 buff.format = format; 361 buff.format.target = GL_TEXTURE_3D; 362 buff.size = TextureSize(size, size, size); 363 buff.mipLevels = 1; 364 buff.data = img.data; 365 createFromBuffer(buff, false); 366 minFilter = GL_LINEAR; 367 magFilter = GL_LINEAR; 368 wrapS = GL_CLAMP_TO_EDGE; 369 wrapT = GL_CLAMP_TO_EDGE; 370 wrapR = GL_CLAMP_TO_EDGE; 371 } 372 373 void createFromBuffer(TextureBuffer buff, bool genMipmaps) 374 { 375 release(); 376 377 this.generateMipmaps = genMipmaps; 378 379 this.format = buff.format; 380 this.size = buff.size; 381 this.mipLevels = buff.mipLevels; 382 383 if (isCubemap) 384 createCubemap(buff.data); 385 else if (format.target == GL_TEXTURE_1D) 386 createTexture1D(buff.data); 387 else if (format.target == GL_TEXTURE_2D) 388 createTexture2D(buff.data); 389 else if (format.target == GL_TEXTURE_3D) 390 createTexture3D(buff.data); 391 else 392 writeln("Texture creation failed: unsupported target ", format.target); 393 } 394 395 protected void createCubemap(ubyte[] buffer) 396 { 397 glGenTextures(1, &texture); 398 glActiveTexture(GL_TEXTURE0); 399 glBindTexture(GL_TEXTURE_CUBE_MAP, texture); 400 401 minFilter = GL_LINEAR; 402 magFilter = GL_LINEAR; 403 wrapS = GL_CLAMP_TO_EDGE; 404 wrapT = GL_CLAMP_TO_EDGE; 405 wrapR = GL_CLAMP_TO_EDGE; 406 407 if (isCompressed) 408 { 409 if (mipLevels > 1) 410 { 411 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); 412 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, mipLevels - 1); 413 } 414 415 uint offset = 0; 416 417 foreach(cubeFace; EnumMembers!CubeFace) 418 { 419 uint w = size.width; 420 uint h = size.height; 421 422 if (mipLevels == 1) 423 { 424 uint size = ((w + 3) / 4) * ((h + 3) / 4) * format.blockSize; 425 glCompressedTexImage2D(cubeFace, 0, format.internalFormat, w, h, 0, cast(uint)buffer.length, cast(void*)buffer.ptr); 426 offset += size; 427 } 428 else 429 { 430 for (uint mipLevel = 0; mipLevel < mipLevels; mipLevel++) 431 { 432 uint imageSize = ((w + 3) / 4) * ((h + 3) / 4) * format.blockSize; 433 glCompressedTexImage2D(cubeFace, mipLevel, format.internalFormat, w, h, 0, imageSize, cast(void*)(buffer.ptr + offset)); 434 offset += imageSize; 435 w /= 2; 436 h /= 2; 437 } 438 } 439 } 440 } 441 else 442 { 443 uint pSize = pixelSize; 444 uint offset = 0; 445 446 foreach(cubeFace; EnumMembers!CubeFace) 447 { 448 uint w = size.width; 449 uint h = size.height; 450 451 for (uint mipLevel = 0; mipLevel < mipLevels; mipLevel++) 452 { 453 uint size = w * h * pSize; 454 glTexImage2D(cubeFace, mipLevel, format.internalFormat, w, h, 0, format.format, format.pixelType, cast(void*)(buffer.ptr + offset)); 455 offset += size; 456 w /= 2; 457 h /= 2; 458 if (offset >= buffer.length) 459 { 460 writeln("Error: incomplete texture buffer"); 461 break; 462 } 463 } 464 } 465 } 466 467 glBindTexture(GL_TEXTURE_CUBE_MAP, 0); 468 469 if (mipLevels > 1) 470 { 471 minFilter = GL_LINEAR_MIPMAP_LINEAR; 472 magFilter = GL_LINEAR; 473 } 474 else 475 { 476 minFilter = GL_LINEAR; 477 magFilter = GL_LINEAR; 478 } 479 } 480 481 protected void createTexture1D(ubyte[] buffer) 482 { 483 glGenTextures(1, &texture); 484 glActiveTexture(GL_TEXTURE0); 485 glBindTexture(GL_TEXTURE_1D, texture); 486 487 minFilter = GL_LINEAR; 488 magFilter = GL_LINEAR; 489 wrapS = GL_REPEAT; 490 wrapT = GL_REPEAT; 491 wrapR = GL_REPEAT; 492 493 uint w = size.width; 494 495 if (isCompressed) 496 { 497 if (mipLevels == 1) 498 { 499 glCompressedTexImage1D(GL_TEXTURE_1D, 0, format.internalFormat, w, 0, cast(uint)buffer.length, cast(void*)buffer.ptr); 500 } 501 else 502 { 503 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_BASE_LEVEL, 0); 504 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, mipLevels - 1); 505 506 uint offset = 0; 507 508 for (uint mipLevel = 0; mipLevel < mipLevels; mipLevel++) 509 { 510 uint imageSize = ((w + 3) / 4) * format.blockSize; 511 512 glCompressedTexImage1D(GL_TEXTURE_1D, mipLevel, format.internalFormat, w, 0, imageSize, cast(void*)(buffer.ptr + offset)); 513 514 offset += imageSize; 515 w /= 2; 516 } 517 } 518 } 519 else 520 { 521 if (mipLevels == 1) 522 { 523 glTexImage1D(GL_TEXTURE_1D, 0, format.internalFormat, w, 0, format.format, format.pixelType, cast(void*)buffer.ptr); 524 525 if (generateMipmaps) 526 { 527 glGenerateMipmap(GL_TEXTURE_1D); 528 mipLevels = 1 + cast(uint)floor(log2(cast(double)w)); 529 } 530 else 531 mipLevels = 1; 532 } 533 } 534 } 535 536 protected void createTexture2D(ubyte[] buffer) 537 { 538 glGenTextures(1, &texture); 539 glActiveTexture(GL_TEXTURE0); 540 glBindTexture(GL_TEXTURE_2D, texture); 541 542 minFilter = GL_LINEAR; 543 magFilter = GL_LINEAR; 544 wrapS = GL_REPEAT; 545 wrapT = GL_REPEAT; 546 wrapR = GL_REPEAT; 547 548 uint w = size.width; 549 uint h = size.height; 550 551 if (isCompressed) 552 { 553 if (mipLevels == 1) 554 { 555 glCompressedTexImage2D(GL_TEXTURE_2D, 0, format.internalFormat, w, h, 0, cast(uint)buffer.length, cast(void*)buffer.ptr); 556 } 557 else 558 { 559 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 560 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipLevels - 1); 561 562 uint offset = 0; 563 564 for (uint mipLevel = 0; mipLevel < mipLevels; mipLevel++) 565 { 566 uint imageSize = ((w + 3) / 4) * ((h + 3) / 4) * format.blockSize; 567 glCompressedTexImage2D(GL_TEXTURE_2D, mipLevel, format.internalFormat, w, h, 0, imageSize, cast(void*)(buffer.ptr + offset)); 568 offset += imageSize; 569 w /= 2; 570 h /= 2; 571 } 572 } 573 } 574 else 575 { 576 if (mipLevels == 1) 577 { 578 glTexImage2D(GL_TEXTURE_2D, 0, format.internalFormat, w, h, 0, format.format, format.pixelType, cast(void*)buffer.ptr); 579 580 if (generateMipmaps) 581 { 582 glGenerateMipmap(GL_TEXTURE_2D); 583 mipLevels = 1 + cast(uint)floor(log2(cast(double)max(w, h))); 584 } 585 else 586 mipLevels = 1; 587 } 588 else if (channelSize > 0) 589 { 590 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 591 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipLevels - 1); 592 593 uint pSize = pixelSize; 594 uint offset = 0; 595 596 for (uint mipLevel = 0; mipLevel < mipLevels; mipLevel++) 597 { 598 uint imageSize = w * h * pSize; 599 glTexImage2D(GL_TEXTURE_2D, mipLevel, format.internalFormat, w, h, 0, format.format, format.pixelType, cast(void*)(buffer.ptr + offset)); 600 offset += imageSize; 601 w /= 2; 602 h /= 2; 603 if (offset >= buffer.length) 604 { 605 writeln("Error: incomplete texture buffer"); 606 break; 607 } 608 } 609 } 610 } 611 612 if (mipLevels > 1) 613 { 614 minFilter = GL_LINEAR_MIPMAP_LINEAR; 615 magFilter = GL_LINEAR; 616 } 617 else 618 { 619 minFilter = GL_LINEAR; 620 magFilter = GL_LINEAR; 621 } 622 } 623 624 protected void createTexture3D(ubyte[] buffer) 625 { 626 glGenTextures(1, &texture); 627 glActiveTexture(GL_TEXTURE0); 628 glBindTexture(GL_TEXTURE_3D, texture); 629 630 minFilter = GL_LINEAR; 631 magFilter = GL_LINEAR; 632 wrapS = GL_REPEAT; 633 wrapT = GL_REPEAT; 634 wrapR = GL_REPEAT; 635 636 uint w = size.width; 637 uint h = size.height; 638 uint d = size.depth; 639 640 if (isCompressed) 641 { 642 writeln("Compressed 3D textures are not supported"); 643 } 644 else 645 { 646 if (mipLevels == 1) 647 { 648 glTexImage3D(GL_TEXTURE_3D, 0, format.internalFormat, w, h, d, 0, format.format, format.pixelType, cast(void*)buffer.ptr); 649 650 if (generateMipmaps) 651 { 652 glGenerateMipmap(GL_TEXTURE_3D); 653 mipLevels = 1 + cast(uint)floor(log2(cast(double)max3(w, h, d))); 654 } 655 else 656 mipLevels = 1; 657 } 658 else if (channelSize > 0) 659 { 660 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 661 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipLevels - 1); 662 663 uint pSize = pixelSize; 664 uint offset = 0; 665 666 for (uint mipLevel = 0; mipLevel < mipLevels; mipLevel++) 667 { 668 uint imageSize = w * h * d * pSize; 669 glTexImage3D(GL_TEXTURE_3D, mipLevel, format.internalFormat, w, h, d, 0, format.format, format.pixelType, cast(void*)(buffer.ptr + offset)); 670 offset += imageSize; 671 w /= 2; 672 h /= 2; 673 d /= 2; 674 if (offset >= buffer.length) 675 { 676 writeln("Error: incomplete texture buffer"); 677 break; 678 } 679 } 680 } 681 } 682 } 683 684 void createFromEquirectangularMap(SuperImage envmap, uint resolution, bool generateMipmaps = true) 685 { 686 glGenTextures(1, &texture); 687 glActiveTexture(GL_TEXTURE0); 688 glBindTexture(GL_TEXTURE_CUBE_MAP, texture); 689 690 minFilter = GL_LINEAR; 691 magFilter = GL_LINEAR; 692 wrapS = GL_CLAMP_TO_EDGE; 693 wrapT = GL_CLAMP_TO_EDGE; 694 wrapR = GL_CLAMP_TO_EDGE; 695 696 TextureFormat tf; 697 if (detectTextureFormat(envmap, tf)) 698 { 699 format.cubeFaces = CubeFaceBit.All; 700 SuperImage faceImage = envmap.createSameFormat(resolution, resolution); 701 702 foreach(i, face; EnumMembers!CubeFace) 703 { 704 Matrix4x4f dirTransform = cubeFaceMatrix(face); 705 706 foreach(x; 0..resolution) 707 foreach(y; 0..resolution) 708 { 709 float cubex = (cast(float)x / cast(float)resolution) * 2.0f - 1.0f; 710 float cubey = (1.0f - cast(float)y / cast(float)resolution) * 2.0f - 1.0f; 711 Vector3f dir = Vector3f(cubex, cubey, 1.0f).normalized * dirTransform; 712 Vector2f uv = equirectProj(dir); 713 Color4f c = bilinearPixel(envmap, uv.x * envmap.width, uv.y * envmap.height); 714 faceImage[x, y] = c; 715 } 716 717 glTexImage2D(face, 0, tf.internalFormat, resolution, resolution, 0, tf.format, tf.pixelType, cast(void*)faceImage.data.ptr); 718 } 719 720 Delete(faceImage); 721 722 if (generateMipmaps) 723 { 724 glGenerateMipmap(GL_TEXTURE_CUBE_MAP); 725 mipLevels = 1 + cast(uint)floor(log2(cast(double)resolution)); 726 } 727 else 728 mipLevels = 1; 729 } 730 else 731 { 732 writefln("Unsupported pixel format %s", envmap.pixelFormat); 733 } 734 735 glBindTexture(GL_TEXTURE_CUBE_MAP, 0); 736 } 737 738 void generateMipmap() 739 { 740 if (valid) 741 { 742 bind(); 743 glGenerateMipmap(format.target); 744 mipLevels = 1 + cast(uint)floor(log2(cast(double)max(size.width, size.height))); 745 unbind(); 746 useMipmapFiltering(true); 747 } 748 } 749 750 void createFallbackTexture() 751 { 752 // TODO 753 } 754 755 void release() 756 { 757 if (valid) 758 glDeleteTextures(1, &texture); 759 } 760 761 deprecated("use Texture.release instead") alias releaseGLTexture = release; 762 763 bool valid() 764 { 765 return cast(bool)glIsTexture(texture); 766 } 767 768 void bind() 769 { 770 if (valid) 771 { 772 if (isCubemap) 773 { 774 glBindTexture(GL_TEXTURE_CUBE_MAP, texture); 775 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, minFilter); 776 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, magFilter); 777 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, wrapS); 778 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, wrapT); 779 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, wrapR); 780 } 781 else if (dimension == TextureDimension.D1) 782 { 783 glBindTexture(GL_TEXTURE_1D, texture); 784 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, minFilter); 785 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, magFilter); 786 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, wrapS); 787 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, wrapT); 788 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_R, wrapR); 789 } 790 else if (dimension == TextureDimension.D2) 791 { 792 glBindTexture(GL_TEXTURE_2D, texture); 793 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); 794 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); 795 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS); 796 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT); 797 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, wrapR); 798 } 799 else if (dimension == TextureDimension.D3) 800 { 801 glBindTexture(GL_TEXTURE_3D, texture); 802 glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, minFilter); 803 glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, magFilter); 804 glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, wrapS); 805 glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, wrapT); 806 glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, wrapR); 807 } 808 } 809 } 810 811 void unbind() 812 { 813 if (isCubemap) 814 glBindTexture(GL_TEXTURE_CUBE_MAP, 0); 815 else if (dimension == TextureDimension.D1) 816 glBindTexture(GL_TEXTURE_1D, 0); 817 else if (dimension == TextureDimension.D2) 818 glBindTexture(GL_TEXTURE_2D, 0); 819 else if (dimension == TextureDimension.D3) 820 glBindTexture(GL_TEXTURE_3D, 0); 821 } 822 823 uint width() @property 824 { 825 return size.width; 826 } 827 828 uint height() @property 829 { 830 return size.height; 831 } 832 833 uint numChannels() @property 834 { 835 if (format.format in numChannelsFormat) 836 return numChannelsFormat[format.format]; 837 else 838 return 0; 839 } 840 841 bool hasAlpha() @property 842 { 843 return (numChannels == 4); 844 } 845 846 bool isCompressed() @property 847 { 848 return compressedFormats.canFind(format.internalFormat); 849 } 850 851 bool isCubemap() @property 852 { 853 return format.cubeFaces != CubeFaceBit.None; 854 } 855 856 TextureDimension dimension() @property 857 { 858 if (format.target == GL_TEXTURE_1D) 859 return TextureDimension.D1; 860 else if (format.target == GL_TEXTURE_2D) 861 return TextureDimension.D2; 862 else if (format.target == GL_TEXTURE_3D) 863 return TextureDimension.D3; 864 else 865 return TextureDimension.Undefined; 866 } 867 868 uint channelSize() @property 869 { 870 uint s = 0; 871 switch(format.pixelType) 872 { 873 case GL_UNSIGNED_BYTE: s = 1; break; 874 case GL_BYTE: s = 1; break; 875 case GL_UNSIGNED_SHORT: s = 2; break; 876 case GL_SHORT: s = 2; break; 877 case GL_UNSIGNED_INT: s = 4; break; 878 case GL_INT: s = 4; break; 879 case GL_HALF_FLOAT: s = 2; break; 880 case GL_FLOAT: s = 4; break; 881 default: s = 0; break; 882 } 883 return s; 884 } 885 886 uint pixelSize() @property 887 { 888 return numChannels * channelSize; 889 } 890 891 bool useMipmapFiltering() @property 892 { 893 return minFilter == GL_LINEAR_MIPMAP_LINEAR; 894 } 895 896 void useMipmapFiltering(bool mode) @property 897 { 898 if (mode) 899 minFilter = GL_LINEAR_MIPMAP_LINEAR; 900 else 901 minFilter = GL_LINEAR; 902 } 903 904 void enableRepeat(bool mode) @property 905 { 906 if (mode) 907 { 908 wrapS = GL_REPEAT; 909 wrapT = GL_REPEAT; 910 wrapR = GL_REPEAT; 911 } 912 else 913 { 914 wrapS = GL_CLAMP_TO_EDGE; 915 wrapT = GL_CLAMP_TO_EDGE; 916 wrapR = GL_CLAMP_TO_EDGE; 917 } 918 } 919 920 void setFaceBit(CubeFace face) 921 { 922 format.cubeFaces = format.cubeFaces | cubeFaceBit(face); 923 } 924 925 void setFaceImage(CubeFace face, SuperImage img) 926 { 927 if (img.width != img.height) 928 { 929 writeln("Cubemap face image must be square"); 930 return; 931 } 932 933 TextureFormat tf; 934 if (!detectTextureFormat(img, tf)) 935 { 936 writeln("Unsupported image format ", img.pixelFormat); 937 return; 938 } 939 940 if (!valid) 941 { 942 format.target = GL_TEXTURE_CUBE_MAP; 943 944 // TODO: store individual size and format for each face 945 946 size.width = img.width; 947 size.height = img.height; 948 949 format.format = tf.format; 950 format.internalFormat = tf.internalFormat; 951 format.pixelType = tf.pixelType; 952 format.blockSize = tf.blockSize; 953 954 glGenTextures(1, &texture); 955 956 minFilter = GL_LINEAR; 957 magFilter = GL_LINEAR; 958 wrapS = GL_CLAMP_TO_EDGE; 959 wrapT = GL_CLAMP_TO_EDGE; 960 wrapR = GL_CLAMP_TO_EDGE; 961 } 962 963 setFaceBit(face); 964 965 glActiveTexture(GL_TEXTURE0); 966 glBindTexture(GL_TEXTURE_CUBE_MAP, texture); 967 if (isCompressed) 968 { 969 uint size = ((img.width + 3) / 4) * ((img.height + 3) / 4) * tf.blockSize; 970 glCompressedTexImage2D(face, 0, tf.internalFormat, img.width, img.height, 0, size, cast(void*)img.data.ptr); 971 } 972 else 973 { 974 glTexImage2D(face, 0, tf.internalFormat, img.width, img.height, 0, tf.format, tf.pixelType, cast(void*)img.data.ptr); 975 } 976 glBindTexture(GL_TEXTURE_CUBE_MAP, 0); 977 } 978 } 979 980 bool detectTextureFormat(SuperImage img, out TextureFormat tf) 981 { 982 uint pixelFormat = img.pixelFormat; 983 switch(pixelFormat) 984 { 985 case IntegerPixelFormat.L8: tf.internalFormat = GL_R8; tf.format = GL_RED; tf.pixelType = GL_UNSIGNED_BYTE; break; 986 case IntegerPixelFormat.LA8: tf.internalFormat = GL_RG8; tf.format = GL_RG; tf.pixelType = GL_UNSIGNED_BYTE; break; 987 case IntegerPixelFormat.RGB8: tf.internalFormat = GL_RGB8; tf.format = GL_RGB; tf.pixelType = GL_UNSIGNED_BYTE; break; 988 case IntegerPixelFormat.RGBA8: tf.internalFormat = GL_RGBA8; tf.format = GL_RGBA; tf.pixelType = GL_UNSIGNED_BYTE; break; 989 case FloatPixelFormat.RGBAF32: tf.internalFormat = GL_RGBA32F; tf.format = GL_RGBA; tf.pixelType = GL_FLOAT; break; 990 default: 991 return false; 992 } 993 994 tf.target = GL_TEXTURE_2D; 995 tf.blockSize = 0; 996 tf.cubeFaces = CubeFaceBit.None; 997 998 return true; 999 } 1000 1001 Matrix4x4f cubeFaceMatrix(CubeFace cf) 1002 { 1003 switch(cf) 1004 { 1005 case CubeFace.PositiveX: 1006 return rotationMatrix(1, degtorad(-90.0f)); 1007 case CubeFace.NegativeX: 1008 return rotationMatrix(1, degtorad(90.0f)); 1009 case CubeFace.PositiveY: 1010 return rotationMatrix(0, degtorad(90.0f)); 1011 case CubeFace.NegativeY: 1012 return rotationMatrix(0, degtorad(-90.0f)); 1013 case CubeFace.PositiveZ: 1014 return rotationMatrix(1, degtorad(0.0f)); 1015 case CubeFace.NegativeZ: 1016 return rotationMatrix(1, degtorad(180.0f)); 1017 default: 1018 return Matrix4x4f.identity; 1019 } 1020 } 1021 1022 Matrix4x4f cubeFaceCameraMatrix(CubeFace cf, Vector3f pos) 1023 { 1024 Matrix4x4f m; 1025 switch(cf) 1026 { 1027 case CubeFace.PositiveX: 1028 m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(90.0f)) * rotationMatrix(2, degtorad(180.0f)); 1029 break; 1030 case CubeFace.NegativeX: 1031 m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(-90.0f)) * rotationMatrix(2, degtorad(180.0f)); 1032 break; 1033 case CubeFace.PositiveY: 1034 m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(-90.0f)); 1035 break; 1036 case CubeFace.NegativeY: 1037 m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(90.0f)); 1038 break; 1039 case CubeFace.PositiveZ: 1040 m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(180.0f)) * rotationMatrix(2, degtorad(180.0f)); 1041 break; 1042 case CubeFace.NegativeZ: 1043 m = rotationMatrix(1, degtorad(90.0f)) * translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(2, degtorad(180.0f)); 1044 break; 1045 default: 1046 m = Matrix4x4f.identity; break; 1047 } 1048 return m; 1049 } 1050 1051 Vector2f equirectProj(Vector3f dir) 1052 { 1053 float phi = acos(dir.y); 1054 float theta = atan2(dir.x, dir.z) + PI; 1055 return Vector2f(theta / (PI * 2.0f), phi / PI); 1056 }