1 /*
2     Copyright © 2020, Luna Nielsen
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module engine.render.texture;
8 public import engine.render.texture.packer;
9 public import engine.render.texture.atlas;
10 public import engine.render.texture.font;
11 import bindbc.opengl;
12 import std.exception;
13 import imagefmt;
14 import std.format;
15 import engine;
16 
17 /**
18     Filtering mode for texture
19 */
20 enum Filtering {
21     /**
22         Linear filtering will try to smooth out textures
23     */
24     Linear = GL_LINEAR,
25 
26     /**
27         Point filtering will try to preserve pixel edges.
28         Due to texture sampling being float based this is imprecise.
29     */
30     Point = GL_POINT
31 }
32 
33 /**
34     Texture wrapping modes
35 */
36 enum Wrapping {
37     /**
38         Clamp texture sampling to be within the texture
39     */
40     Clamp = GL_CLAMP_TO_BORDER,
41 
42     /**
43         Wrap the texture in every direction idefinitely
44     */
45     Repeat = GL_REPEAT,
46 
47     /**
48         Wrap the texture mirrored in every direction indefinitely
49     */
50     Mirror = GL_MIRRORED_REPEAT
51 }
52 
53 /**
54     A texture which is not bound to an OpenGL context
55 
56     Used for texture atlassing
57 */
58 struct ShallowTexture {
59 public:
60     /**
61         8-bit RGBA color data
62     */
63     ubyte[] data;
64 
65     /**
66         Width of texture
67     */
68     int width;
69 
70     /**
71         Height of texture
72     */
73     int height;
74 
75     /**
76         Loads a shallow texture from image file
77         Supported file types:
78         * PNG 8-bit
79         * BMP 8-bit
80         * TGA 8-bit non-palleted
81         * JPEG baseline
82     */
83     this(string file) {
84 
85         // Load image from disk, as RGBA 8-bit
86         IFImage image = read_image(file, 4, 8);
87         enforce( image.e == 0, "%s: %s".format(IF_ERROR[image.e], file));
88         scope(exit) image.free();
89 
90         // Copy data from IFImage to this ShallowTexture
91         this.data = new ubyte[image.buf8.length];
92         this.data[] = image.buf8;
93 
94         // Set the width/height data
95         this.width = image.w;
96         this.height = image.h;
97     }
98 }
99 
100 /**
101     A texture, only format supported is unsigned 8 bit RGBA
102 */
103 class Texture {
104 private:
105     GLuint id;
106     int width_;
107     int height_;
108 
109     GLuint colorMode;
110     int alignment;
111 
112 public:
113 
114     /**
115         Loads texture from image file
116         Supported file types:
117         * PNG 8-bit
118         * BMP 8-bit
119         * TGA 8-bit non-palleted
120         * JPEG baseline
121     */
122     this(string file) {
123 
124         // Load image from disk, as RGBA 8-bit
125         IFImage image = read_image(file, 4, 8);
126         enforce( image.e == 0, "%s: %s".format(IF_ERROR[image.e], file));
127         scope(exit) image.free();
128 
129         // Load in image data to OpenGL
130         this(image.buf8, image.w, image.h);
131     }
132 
133     /**
134         Creates a texture from a ShallowTexture
135     */
136     this(ShallowTexture shallow) {
137         this(shallow.data, shallow.width, shallow.height);
138     }
139 
140     /**
141         Creates a new empty texture
142     */
143     this(int width, int height, GLuint mode = GL_RGBA, int alignment = 4) {
144 
145         // Create an empty texture array with no data
146         ubyte[] empty = new ubyte[width_*height_*alignment];
147 
148         // Pass it on to the other texturing
149         this(empty, width, height, mode, alignment);
150     }
151 
152     /**
153         Creates a new texture from specified data
154     */
155     this(ubyte[] data, int width, int height, GLuint mode = GL_RGBA, int alignment = 4) {
156         this.colorMode = mode;
157         this.alignment = alignment;
158         this.width_ = width;
159         this.height_ = height;
160         
161         // Generate OpenGL texture
162         glGenTextures(1, &id);
163         this.setData(data);
164 
165         // Set default filtering and wrapping
166         this.setFiltering(Filtering.Linear);
167         this.setWrapping(Wrapping.Clamp);
168     }
169 
170     /**
171         Width of texture
172     */
173     int width() {
174         return width_;
175     }
176 
177     /**
178         Height of texture
179     */
180     int height() {
181         return height_;
182     }
183 
184     /**
185         Set the filtering mode used for the texture
186     */
187     void setFiltering(Filtering filtering) {
188         this.bind();
189         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
190         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
191     }
192 
193     /**
194         Set the wrapping mode used for the texture
195     */
196     void setWrapping(Wrapping wrapping) {
197         this.bind();
198         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapping);
199         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapping);
200     }
201 
202     /**
203         Sets the data of the texture
204     */
205     void setData(ubyte[] data) {
206         this.bind();
207         glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
208         glTexImage2D(GL_TEXTURE_2D, 0, colorMode, width_, height_, 0, colorMode, GL_UNSIGNED_BYTE, data.ptr);
209         glGenerateMipmap(GL_TEXTURE_2D);
210     }
211 
212     /**
213         Sets a region of a texture to new data
214     */
215     void setDataRegion(ubyte[] data, int x, int y, int width, int height) {
216         this.bind();
217 
218         // Make sure we don't try to change the texture in an out of bounds area.
219         enforce( x >= 0 && x+width <= this.width_, "x offset is out of bounds (xoffset=%s, xbound=%s)".format(x+width, this.width_));
220         enforce( y >= 0 && y+height <= this.height_, "y offset is out of bounds (yoffset=%s, ybound=%s)".format(y+height, this.height_));
221 
222         // Update the texture
223         glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
224         glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, this.colorMode, GL_UNSIGNED_BYTE, data.ptr);
225         glGenerateMipmap(GL_TEXTURE_2D);
226     }
227 
228     /**
229         Bind this texture
230         
231         Notes
232         - In release mode the unit value is clamped to 31 (The max OpenGL texture unit value)
233         - In debug mode unit values over 31 will assert.
234     */
235     void bind(uint unit = 0) {
236         assert(unit <= 31u, "Outside maximum OpenGL texture unit value");
237         glActiveTexture(GL_TEXTURE0+(unit <= 31u ? unit : 31u));
238         glBindTexture(GL_TEXTURE_2D, id);
239     }
240 }