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 }