1 module glamour.texture;
2 
3 private {
4     import glamour.gl : GLenum, GLuint, GLint, GLsizei, GL_UNSIGNED_BYTE,
5                         glGenTextures, glBindTexture, glActiveTexture,
6                         glTexImage1D, glTexImage2D, glTexParameteri, glTexParameterf,
7                         glGetTexParameterfv, glDeleteTextures, GL_TEXTURE0,
8                         GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, GL_PROXY_TEXTURE_2D_ARRAY,
9                         GL_PROXY_TEXTURE_3D, GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR,
10                         GL_RGBA8, GL_RGB, GL_RGBA, glGenerateMipmap;
11     
12     version(stb) {
13         import stb_image : stbi_load, stbi_image_free;
14     } else version(SDLImage2) {
15         import derelict.sdl2.sdl;
16         import derelict.sdl2.image;
17         version = _glad_sdl;
18     } else version(SDLImage) {
19         import derelict.sdl.sdl;
20         import derelict.sdl.image;
21         version = _glad_sdl;
22     }
23     
24     import glamour.util : glenum2size, checkgl;
25     import std.traits : isPointer;
26     import std..string : toStringz;
27     import std.exception : enforce;
28     import std.conv : to;
29 
30     debug import std.stdio : stderr;
31 }
32 
33 
34 /// This exception will be thrown if Texture2D.from_image fails to open the image.
35 class TextureException : Exception {
36     this(string msg) {
37         super(msg);
38     }
39 }
40 deprecated alias TextureException TextureError;
41 
42 /// Every Texture-Class will mixin this template.
43 mixin template CommonTextureMethods() {
44     ~this() {
45         debug if(texture != 0) stderr.writefln("OpenGL: %s resources not released.", typeof(this).stringof);
46     }
47 
48     /// Deletes the texture.
49     void remove() {
50         checkgl!glDeleteTextures(1, &texture);
51         texture = 0;
52     }
53     
54     /// Sets a texture parameter.
55     void set_parameter(T)(GLuint name, T params) if(is(T : int) || is(T : float)) {
56         static if(is(T : int)) {
57             checkgl!glTexParameteri(target, name, params);
58         } else {
59             checkgl!glTexParameterf(target, name, params);
60         }
61     }
62     
63     /// Queries a texture parameter from OpenGL.    
64     float[] get_parameter(GLuint name) {
65         float[] ret;
66         
67         if(name == GL_TEXTURE_BORDER_COLOR) {
68             ret.length = 4;
69         } else {
70             ret.length = 1;
71         }
72         
73         checkgl!glGetTexParameterfv(target, name, ret.ptr);
74         return ret;
75     }
76     
77     /// Generates mipmaps for the textre (also binds it)
78     void generate_mipmaps() {
79         bind();
80         checkgl!glGenerateMipmap(target);
81     }
82 
83     /// Binds the texture.
84     void bind() {
85         checkgl!glBindTexture(target, texture);
86     }
87     
88     /// Activates the texture to $(I unit), passed to the function. 
89     void activate(GLuint unit) {
90         checkgl!glActiveTexture(unit);
91     }
92     
93     /// Activates the texture to $(B unit), the struct member.
94     void activate() {
95         activate(unit);
96     }
97     
98     /// Binds the texture and activates it to $(I unit), passed to the function. 
99     void bind_and_activate(GLuint unit) {
100         checkgl!glBindTexture(target, texture);
101         checkgl!glActiveTexture(unit);
102     }
103     
104     /// Binds the texture and activates it to $(B unit), the struct member.
105     void bind_and_activate() {
106         bind_and_activate(unit);
107     }
108     
109     /// Unbinds the texture.
110     void unbind() {
111         checkgl!glBindTexture(target, 0);
112     }
113 }
114 
115 /// Interface every Texture implements.
116 interface ITexture {
117     GLuint get_unit(); ///
118     void remove(); ///
119     void set_parameter(T)(GLuint name, T params); ///
120     float[] get_parameter(GLuint name); /// 
121     void set_data(T)(T data); /// 
122     void generate_mipmaps(); ///
123     void bind(); /// 
124     void activate(GLuint unit); /// 
125     void activate(); /// 
126     void bind_and_activate(GLuint unit); /// 
127     void bind_and_activate(); /// 
128     void unbind(); /// 
129 }
130 
131 
132 /// Represents an OpenGL 1D texture.
133 /// The constructor must be used to avoid segmentation faults.
134 class Texture1D : ITexture {
135     static const GLenum target = GL_TEXTURE_1D;
136     
137     /// The OpenGL texture name.
138     GLuint texture;
139     /// Alias this to texture.
140     alias texture this;
141     
142     /// Holds the internal format passed to the constructor.
143     GLint internal_format;
144     /// Holds the format of the pixel data.
145     GLenum format;
146     /// Holds the OpenGL data type of the pixel data.
147     GLenum type;
148     /// Holds the texture unit.
149     GLuint unit;
150     GLuint get_unit() { return unit; }
151     
152     ///
153     mixin CommonTextureMethods;
154     
155     /// Generates the OpenGL texture and initializes the struct.
156     /// Params:
157     /// unit_ = Specifies the OpenGL texture uinit.
158     this(GLenum unit=GL_TEXTURE0) {
159         this.unit = unit;
160         
161         checkgl!glGenTextures(1, &texture);
162     }
163         
164     /// Sets the texture data.
165     /// Params:
166     /// data = A pointer to the image data or an array of the image data.
167     /// internal_format = Specifies the number of color components in the texture.
168     /// width = If data is an array and width is -1, the width will be infered from the array, otherwise it must be valid.
169     /// format = Specifies the format of the pixel data.
170     /// type = Specifies the data type of the pixel data.
171     ///
172     /// See_Also:
173     /// OpenGL, http://www.opengl.org/sdk/docs/man4/xhtml/glTexImage1D.xml
174     void set_data(T)(T data, GLint internal_format, int width, GLenum format, GLenum type) {
175         bind();
176 
177         this.internal_format = internal_format;
178         this.format = format;
179         this.type = type;
180         
181         static if(isPointer!T) {
182             auto d = data;
183         } else {
184             auto d = data.ptr;
185 
186             if(width == -1) {
187                 width = cast(int)data.length;
188             }
189         }
190 
191         checkgl!glTexImage1D(GL_TEXTURE_1D, 0, internal_format, width, 0, format, type, d);
192     }
193 }
194 
195 /// Represents an OpenGL 2D texture.
196 /// The constructor must be used to avoid segmentation faults.
197 class Texture2D : ITexture {
198     static const GLenum target = GL_TEXTURE_2D;
199 
200     /// The OpenGL texture name.
201     GLuint texture;
202     /// Alias this to texture.
203     alias texture this;
204 
205     /// Holds the internal format passed to the constructor.
206     GLint internal_format;
207     /// Holds the texture width.
208     GLsizei width;
209     /// Holds the texture height.
210     GLsizei height;
211     /// Holds the format of the pixel data.
212     GLenum format;
213     /// Holds the OpenGL data type of the pixel data.
214     GLenum type;
215     /// Holds the texture unit.
216     GLenum unit;
217     GLuint get_unit() { return unit; }
218     /// If true (default) mipmaps will be generated with glGenerateMipmap.
219     bool mipmaps = true;
220         
221     mixin CommonTextureMethods;
222     
223     /// Generates the OpenGL texture and initializes the struct.
224     /// Params:
225     /// unit = Specifies the OpenGL texture uinit.
226     this(GLenum unit=GL_TEXTURE0) {
227         this.unit = unit;
228 
229         checkgl!glGenTextures(1, &texture);
230     }
231     
232     /// Sets the texture data.
233     /// Params:
234     /// data = A pointer to the image data or an array of the image data.
235     /// internal_format = Specifies the number of color components in the texture.
236     /// width = Specifies the width of the texture image.
237     /// height = Specifies the height of the texture image.
238     /// format = Specifies the format of the pixel data.
239     /// type = Specifies the data type of the pixel data.
240     /// mipmaps = Enables mipmap-generation.
241     /// level = Specifies the level-of-detail number. Level 0 is the base image level. Level n is the n th mipmap reduction image.
242     ///
243     /// See_Also:
244     /// OpenGL, http://www.opengl.org/sdk/docs/man4/xhtml/glTexImage2D.xml
245     ///
246     /// $(RED If mipmaps is true, the gl extensions must be loaded, otherwise bad things will happen!)
247     void set_data(T)(T data, GLint internal_format, GLsizei width, GLsizei height,
248                      GLenum format, GLenum type, bool mipmaps=true, GLint level=0) {
249         bind();
250 
251         this.internal_format = internal_format;
252         this.width = width;
253         this.height = height;
254         this.format = format;
255         this.type = type;
256         this.mipmaps = mipmaps;
257         
258         static if(isPointer!T) {
259             auto d = data;
260         } else {
261             auto d = data.ptr;
262         }
263         
264         checkgl!glTexImage2D(GL_TEXTURE_2D, level, internal_format, width, height, 0, format, type, d);
265         
266         if(mipmaps) {
267             checkgl!glGenerateMipmap(GL_TEXTURE_2D);
268         }
269     }
270     
271     version(stb) {
272         /// Loads an image and afterwards loads it into a Texture2D struct.
273         /// Image can be loaded by stb and SDLImage. To select
274         /// specific way use versions stb, SDLImage2 (SDL2) or SDLImage (SDL1).
275         ///
276         /// $(RED SDLImage must be loaded and initialized manually!)
277         static Texture2D from_image(string filename) {
278             auto tex = new Texture2D();
279 
280             int x;
281             int y;
282             int comp;
283             ubyte* data = stbi_load(toStringz(filename), &x, &y, &comp, 0);
284             scope(exit) stbi_image_free(data);
285 
286             if(data is null) {
287                 throw new TextureException("Unable to load image: " ~ filename);
288             }
289             
290             uint image_format;
291             switch(comp) {
292                 case 3: image_format = GL_RGB; break;
293                 case 4: image_format = GL_RGBA; break;
294                 default: throw new TextureException("Unknown/Unsupported stbi image format");
295             }
296 
297             tex.set_data(data, image_format, x, y, image_format, GL_UNSIGNED_BYTE);
298 
299             return tex;
300         }
301     } else version (_glad_sdl) {
302         static Texture2D from_image(string filename) {
303             auto tex = new Texture2D();
304 
305             // make sure the texture has the right side up
306             //thanks to tito http://stackoverflow.com/questions/5862097/sdl-opengl-screenshot-is-black
307             SDL_Surface* flip(SDL_Surface* surface) {
308                 SDL_Surface* result = SDL_CreateRGBSurface(surface.flags, surface.w, surface.h,
309                                                         surface.format.BytesPerPixel * 8, surface.format.Rmask, surface.format.Gmask,
310                                                         surface.format.Bmask, surface.format.Amask);
311 
312                 ubyte* pixels = cast(ubyte*) surface.pixels;
313                 ubyte* rpixels = cast(ubyte*) result.pixels;
314                 uint pitch = surface.pitch;
315                 uint pxlength = pitch * surface.h;
316 
317                 assert(result != null);
318 
319                 for(uint line = 0; line < surface.h; ++line) {
320                     uint pos = line * pitch;
321                     rpixels[pos..pos+pitch] = pixels[(pxlength-pos)-pitch..pxlength-pos];
322                 }
323 
324                 return result;
325             }
326 
327             auto surface = IMG_Load(filename.toStringz());
328 
329             enforce(surface, new TextureException("Error loading image " ~ filename ~ ": " ~ to!string(SDL_GetError())));
330             scope(exit) SDL_FreeSurface(surface);
331 
332             enforce(surface.format.BytesPerPixel == 3 || surface.format.BytesPerPixel == 4, "With SDLImage Glamour supports loading images only with 3 or 4 bytes per pixel format.");
333             auto image_format = GL_RGB;
334 
335             if (surface.format.BytesPerPixel == 4) {
336             image_format = GL_RGBA;
337             }
338 
339             auto flipped = flip(surface);
340             tex.set_data(flipped.pixels, image_format, surface.w, surface.h, image_format, GL_UNSIGNED_BYTE);
341             SDL_FreeSurface(flipped);
342 
343             return tex;
344         }
345     }
346 }
347 
348 /// Base class, which represents an OpenGL 3D or 2D array texture.
349 /// The constructor must be used to avoid segmentation faults.
350 class Texture3DBase(GLenum target_) : ITexture {
351     static assert(target_ == GL_TEXTURE_3D || target_ == GL_PROXY_TEXTURE_3D ||
352                   target_ == GL_TEXTURE_2D_ARRAY || target_ == GL_PROXY_TEXTURE_2D_ARRAY);
353     
354     static const GLenum target = target_;
355 
356     /// The OpenGL texture name.
357     GLuint texture;
358     /// Alias this to texture.
359     alias texture this;
360 
361     /// Holds the internal format passed to the constructor.
362     GLint internal_format;
363     /// Holds the format of the pixel data.
364     GLenum format;
365     /// Holds the OpenGL data type of the pixel data.
366     GLenum type;
367     /// Holds the texture unit.
368     GLuint unit;
369     GLuint get_unit() { return unit; }
370 
371     ///
372     mixin CommonTextureMethods;
373 
374     /// Generates the OpenGL texture and initializes the struct.
375     /// Params:
376     /// unit = Specifies the OpenGL texture uinit.
377     this(GLenum unit=GL_TEXTURE0) {
378         this.unit = unit;
379 
380         checkgl!glGenTextures(1, &texture);
381     }
382 
383     /// Sets the texture data.
384     /// Params:
385     /// data = A pointer to the image data or an array of the image data.
386     /// internal_format = Specifies the number of color components in the texture.
387     /// width = Specifies the width of the texture image.
388     /// height = Specifies the height of the texture image.
389     /// depth = Specifies the depth of the texture image, or the number of layers in a texture array.
390     /// format = Specifies the format of the pixel data.
391     /// type = Specifies the data type of the pixel data.
392     ///
393     /// See_Also:
394     /// http://www.opengl.org/sdk/docs/man4/xhtml/glTexImage3D.xml
395     void set_data(T)(T data, GLint internal_format, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLint level=0) {
396         bind();
397 
398         this.internal_format = internal_format;
399         this.width = width;
400         this.height = height;
401         this.depth = depth;
402         this.format = format;
403         this.type = type;
404 
405         static if(isPointer!T) {
406             auto d = data;
407         } else {
408             auto d = data.ptr;
409         }
410 
411         checkgl!glTexImage3D(target, level, internal_format, width, height, depth, 0, format, type, d);
412     }
413 }
414 
415 alias Texture3DBase!(GL_TEXTURE_3D) Texture3D;
416 alias Texture3DBase!(GL_PROXY_TEXTURE_2D_ARRAY) Texture2DArray;