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 }