1 /*
2 Copyright (c) 2017-2019 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.compressedimage;
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 class Texture: Owner
63 {
64     SuperImage image;
65 
66     GLuint tex;
67     GLenum format;
68     GLint intFormat;
69     GLenum type;
70 
71     int width;
72     int height;
73     int numMipmapLevels;
74 
75     Vector2f translation;
76     Vector2f scale;
77     float rotation;
78 
79     bool useMipmapFiltering = true;
80     bool useLinearFiltering = true;
81 
82     protected bool mipmapGenerated = false;
83 
84     this(Owner owner)
85     {
86         super(owner);
87         translation = Vector2f(0.0f, 0.0f);
88         scale = Vector2f(1.0f, 1.0f);
89         rotation = 0.0f;
90     }
91 
92     this(SuperImage img, Owner owner, bool genMipmaps = false)
93     {
94         super(owner);
95         translation = Vector2f(0.0f, 0.0f);
96         scale = Vector2f(1.0f, 1.0f);
97         rotation = 0.0f;
98         createFromImage(img, genMipmaps);
99     }
100 
101     void createFromImage(SuperImage img, bool genMipmaps = true)
102     {
103         releaseGLTexture();
104 
105         image = img;
106         width = img.width;
107         height = img.height;
108 
109         glGenTextures(1, &tex);
110         glActiveTexture(GL_TEXTURE0);
111         glBindTexture(GL_TEXTURE_2D, tex);
112 
113         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
114         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
115         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
116         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
117 
118         CompressedImage compressedImg = cast(CompressedImage)img;
119         if (compressedImg)
120         {
121             uint blockSize;
122             uint numMipMaps = compressedImg.mipMapLevels;
123 
124             if (!compressedFormatToTextureFormat(compressedImg.compressedFormat, intFormat, blockSize))
125             {
126                 writeln("Unsupported compressed texture format ", compressedImg.compressedFormat);
127                 fallback();
128             }
129             else
130             {
131                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
132                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, numMipMaps - 1);
133 
134                 uint w = width;
135                 uint h = height;
136                 uint offset = 0;
137                 for (uint i = 0; i < numMipMaps; i++)
138                 {
139                     uint size = ((w + 3) / 4) * ((h + 3) / 4) * blockSize;
140                     glCompressedTexImage2D(GL_TEXTURE_2D, i, intFormat, w, h, 0, size, cast(void*)(img.data.ptr + offset));
141                     offset += size;
142                     w /= 2;
143                     h /= 2;
144                 }
145 
146                 useMipmapFiltering = genMipmaps;
147                 mipmapGenerated = true;
148             }
149         }
150         else
151         {
152             if (!pixelFormatToTextureFormat(img.pixelFormat, format, intFormat, type))
153             {
154                 writeln("Unsupported pixel format ", img.pixelFormat);
155                 fallback();
156             }
157             else
158             {
159                 glTexImage2D(GL_TEXTURE_2D, 0, intFormat, width, height, 0, format, type, cast(void*)img.data.ptr);
160 
161                 useMipmapFiltering = genMipmaps;
162                 if (useMipmapFiltering)
163                 {
164                     glGenerateMipmap(GL_TEXTURE_2D);
165                     mipmapGenerated = true;
166                 }
167             }
168         }
169 
170         glBindTexture(GL_TEXTURE_2D, 0);
171     }
172 
173     protected void fallback()
174     {
175         // TODO: make fallback texture
176     }
177 
178     void bind()
179     {
180         if (glIsTexture(tex))
181         {
182             glBindTexture(GL_TEXTURE_2D, tex);
183 
184             if (!mipmapGenerated && useMipmapFiltering)
185             {
186                 glGenerateMipmap(GL_TEXTURE_2D);
187                 mipmapGenerated = true;
188             }
189 
190             if (useMipmapFiltering)
191                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
192             else if (useLinearFiltering)
193                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
194             else
195             {
196                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
197                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
198             }
199         }
200     }
201 
202     void unbind()
203     {
204         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
205         glBindTexture(GL_TEXTURE_2D, 0);
206     }
207 
208     bool valid()
209     {
210         return cast(bool)glIsTexture(tex);
211     }
212 
213     Color4f sample(float u, float v)
214     {
215         if (image)
216         {
217             int x = cast(int)floor(u * width);
218             int y = cast(int)floor(v * height);
219             return image[x, y];
220         }
221         else
222             return Color4f(0, 0, 0, 0);
223     }
224 
225     void release()
226     {
227         releaseGLTexture();
228         if (image)
229         {
230             Delete(image);
231             image = null;
232         }
233     }
234 
235     void releaseGLTexture()
236     {
237         if (glIsTexture(tex))
238             glDeleteTextures(1, &tex);
239     }
240 
241     ~this()
242     {
243         release();
244     }
245 }
246 
247 bool pixelFormatToTextureFormat(uint pixelFormat, out GLenum textureFormat, out GLint textureInternalFormat, out GLenum pixelType)
248 {
249     switch (pixelFormat)
250     {
251         case PixelFormat.L8:           textureInternalFormat = GL_R8;      textureFormat = GL_RED;  pixelType = GL_UNSIGNED_BYTE; break;
252         case PixelFormat.LA8:          textureInternalFormat = GL_RG8;     textureFormat = GL_RG;   pixelType = GL_UNSIGNED_BYTE; break;
253         case PixelFormat.RGB8:         textureInternalFormat = GL_RGB8;    textureFormat = GL_RGB;  pixelType = GL_UNSIGNED_BYTE; break;
254         case PixelFormat.RGBA8:        textureInternalFormat = GL_RGBA8;   textureFormat = GL_RGBA; pixelType = GL_UNSIGNED_BYTE; break;
255         case FloatPixelFormat.RGBAF32: textureInternalFormat = GL_RGBA32F; textureFormat = GL_RGBA; pixelType = GL_FLOAT; break;
256         default:
257             return false;
258     }
259 
260     return true;
261 }
262 
263 bool compressedFormatToTextureFormat(CompressedImageFormat compFormat, out GLint textureInternalFormat, out uint blockSize)
264 {
265     switch (compFormat)
266     {
267         case CompressedImageFormat.S3TC_RGB_DXT1:    textureInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;         blockSize = 8;  break;
268         case CompressedImageFormat.S3TC_RGBA_DXT3:   textureInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;        blockSize = 16; break;
269         case CompressedImageFormat.S3TC_RGBA_DXT5:   textureInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;        blockSize = 16; break;
270         case CompressedImageFormat.BPTC_RGBA_UNORM:  textureInternalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;       blockSize = 16; break;
271         case CompressedImageFormat.BPTC_SRGBA_UNORM: textureInternalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; blockSize = 16; break;
272         default:
273             return false;
274     }
275 
276     return true;
277 }