1 module d2d.rendering.texture;
2 
3 import d2d;
4 
5 /// Texture filter mode for min and mag filters.
6 enum TextureFilterMode : int
7 {
8 	Linear               = GL_LINEAR,                 /// `GL_LINEAR` sampling. Smooth looking textures.
9 	Nearest              = GL_NEAREST,                /// `GL_NEAREST` sampling. No smoothing.
10 	NearestMipmapNearest = GL_NEAREST_MIPMAP_NEAREST, /// `GL_NEAREST_MIPMAP_NEAREST` sampling. Not usable in mag filter.
11 	LinearMipmapNearest  = GL_LINEAR_MIPMAP_NEAREST,  /// `GL_LINEAR_MIPMAP_NEAREST` sampling. Not usable in mag filter.
12 	NearestMipmapLinear  = GL_NEAREST_MIPMAP_LINEAR,  /// `GL_NEAREST_MIPMAP_LINEAR` sampling. Not usable in mag filter.
13 	LinearMipmapLinear   = GL_LINEAR_MIPMAP_LINEAR,   /// `GL_LINEAR_MIPMAP_LINEAR` sampling. Not usable in mag filter.
14 }
15 
16 /// Texture clamp mode for wrap x, wrap y, wrap z.
17 enum TextureClampMode : int
18 {
19 	ClampToBorder = GL_CLAMP_TO_BORDER, /// Clamps the texture coordinate at the border. Will include a border.
20 	ClampToEdge   = GL_CLAMP_TO_EDGE,   /// Clamps the texture coordinate at the edge of the texture.
21 	Repeat        = GL_REPEAT,          /// Repeats the texture coordinate when being larger than 1.
22 	Mirror        = GL_MIRRORED_REPEAT  /// Repeats the texture coordinate and mirrors every time for better tiling.
23 }
24 
25 /// Texture for drawing using OpenGL.
26 class Texture : IDisposable, IVerifiable
27 {
28 	/// Enable mipmaps. Disabled by default.
29 	public bool enableMipMaps = false;
30 
31 	/// Min Filter for this texture.
32 	/// Needs to call Texture.applyParameters when called after creation.
33 	public TextureFilterMode minFilter = TextureFilterMode.Linear;
34 
35 	/// Mag Filter for this texture.
36 	/// Needs to call Texture.applyParameters when called after creation.
37 	public TextureFilterMode magFilter = TextureFilterMode.Linear;
38 
39 	/// Wrap x for this texture.
40 	/// Needs to call Texture.applyParameters when called after creation.
41 	public TextureClampMode wrapX = TextureClampMode.Repeat;
42 
43 	/// Wrap y Filter for this texture.
44 	/// Needs to call Texture.applyParameters when called after creation.
45 	public TextureClampMode wrapY = TextureClampMode.Repeat;
46 
47 	private int inMode, mode;
48 	private uint _id;
49 	private uint _width, _height;
50 
51 	/// OpenGL id of this texture.
52 	/// id == 0 when not created.
53 	public @property uint id()
54 	{
55 		return _id;
56 	}
57 
58 	/// Width of this texture.
59 	/// width == 0 when not created.
60 	public @property uint width()
61 	{
62 		return _width;
63 	}
64 
65 	/// Height of this texture.
66 	/// height == 0 when not created.
67 	public @property uint height()
68 	{
69 		return _height;
70 	}
71 
72 	/// 1x1 texture containing a white pixel for solid shapes.
73 	public static @property Texture white()
74 	{
75 		return _white;
76 	}
77 
78 	private static Texture _white;
79 
80 	public static void load()
81 	{
82 		_white = new Texture();
83 		_white.create(1, 1, cast(ubyte[])[255, 255, 255, 255]);
84 	}
85 
86 	/// Only allocates memory for the instance but does not create anything.
87 	public this() {}
88 
89 	/// Creates and loads the texture with the given filters.
90 	public this(string file, TextureFilterMode min = TextureFilterMode.Linear, TextureFilterMode mag = TextureFilterMode.Linear, TextureClampMode wrapX = TextureClampMode.Repeat, TextureClampMode wrapY = TextureClampMode.Repeat)
91 	{
92 		if (min == TextureFilterMode.LinearMipmapLinear || min == TextureFilterMode.LinearMipmapNearest)
93 			enableMipMaps = true;
94 		minFilter = min;
95 		magFilter = mag;
96 		this.wrapX = wrapX;
97 		this.wrapY = wrapY;
98 		fromBitmap(Bitmap.load(file));
99 	}
100 
101 	public ~this()
102 	{
103 		dispose();
104 	}
105 
106 	/// Creates a width x height texture containing the pixel data in RGBA ubyte format.
107 	public void create(uint width, uint height, void[] pixels)
108 	{
109 		create(width, height, GL_RGBA, pixels);
110 	}
111 
112 	/// Recreates a width x height texture containing the pixel data in RGBA ubyte format without disposing the old one.
113 	public void recreate(uint width, uint height, void[] pixels)
114 	{
115 		recreate(width, height, GL_RGBA, pixels);
116 	}
117 
118 	/// Creates a width x height texture containing the pixel data using ubytes.
119 	public void create(uint width, uint height, int mode, void[] pixels)
120 	{
121 		glGenTextures(1, &_id);
122 		glBindTexture(GL_TEXTURE_2D, _id);
123 
124 		glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
125 
126 		applyParameters();
127 
128 		this.inMode = mode;
129 		this.mode = mode;
130 		_width = width;
131 		_height = height;
132 
133 		if (!valid)
134 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
135 	}
136 
137 	/// Recreates a width x height texture containing the pixel data using ubytes without disposing the old one.
138 	public void recreate(uint width, uint height, int mode, void[] pixels)
139 	{
140 		bind(0);
141 		glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
142 
143 		applyParameters();
144 
145 		this.inMode = mode;
146 		this.mode = mode;
147 		_width = width;
148 		_height = height;
149 
150 		if (!valid)
151 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
152 	}
153 
154 	/// Creates a width x height texture containing the pixel data in `inMode` format using `type` as array type and internally convertes to `mode`.
155 	public void create(uint width, uint height, int inMode, int mode, void[] pixels, int type = GL_UNSIGNED_BYTE)
156 	{
157 		glGenTextures(1, &_id);
158 		glBindTexture(GL_TEXTURE_2D, _id);
159 
160 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, type, pixels.ptr);
161 
162 		applyParameters();
163 
164 		this.inMode = inMode;
165 		this.mode = mode;
166 		_width = width;
167 		_height = height;
168 
169 		if (!valid)
170 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
171 	}
172 
173 	/// Recreates a width x height texture containing the pixel data in `inMode` format using `type` as array type and internally convertes to `mode` without disposing the old one.
174 	public void recreate(uint width, uint height, int inMode, int mode, void[] pixels, int type = GL_UNSIGNED_BYTE)
175 	{
176 		bind(0);
177 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, type, pixels.ptr);
178 
179 		applyParameters();
180 
181 		this.inMode = inMode;
182 		this.mode = mode;
183 		_width = width;
184 		_height = height;
185 
186 		if (!valid)
187 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
188 	}
189 
190 	/// Use this when changing filter or wrap after creating the texture.
191 	/// Calls automatically after `create`.
192 	public void applyParameters()
193 	{
194 		bind(0);
195 
196 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
197 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
198 
199 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapX);
200 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapY);
201 
202 		if (enableMipMaps)
203 		{
204 			glGenerateMipmap(GL_TEXTURE_2D);
205 			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
206 		}
207 	}
208 
209 	/// Binds the current texture to the given unit.
210 	public void bind(uint unit)
211 	{
212 		glActiveTexture(GL_TEXTURE0 + unit);
213 		glBindTexture(GL_TEXTURE_2D, _id);
214 	}
215 
216 	/// Creates the texture from a bitmap.
217 	public void fromBitmap(Bitmap bitmap, string name = "Bitmap")
218 	{
219 		if (!bitmap.valid)
220 			throw new Exception(name ~ " is invalid!");
221 
222 		int mode = GL_RGB;
223 
224 		if (bitmap.surface.format.BytesPerPixel == 4)
225 		{
226 			mode = GL_RGBA;
227 		}
228 
229 		create(bitmap.width, bitmap.height, mode, bitmap.surface.pixels[0 .. bitmap.width * bitmap.height * bitmap.surface.format.BytesPerPixel]);
230 	}
231 
232 	/// Creates the texture from a bitmap without disposing the old one.
233 	public void recreateFromBitmap(Bitmap bitmap, string name = "Bitmap")
234 	{
235 		if (!bitmap.valid)
236 			throw new Exception(name ~ " is invalid!");
237 
238 		int mode = GL_RGB;
239 
240 		if (bitmap.surface.format.BytesPerPixel == 4)
241 		{
242 			mode = GL_RGBA;
243 		}
244 
245 		recreate(bitmap.width, bitmap.height, mode, bitmap.surface.pixels[0 .. bitmap.width * bitmap.height * bitmap.surface.format.BytesPerPixel]);
246 	}
247 
248 	/// Resizes the texture containing the new data.
249 	public void resize(uint width, uint height, void[] pixels = null)
250 	{
251 		bind(0);
252 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
253 		_width = width;
254 		_height = height;
255 	}
256 
257 	/// Deletes the texture and invalidates `this`.
258 	public void dispose()
259 	{
260 		if (valid)
261 		{
262 			glDeleteTextures(1, &_id);
263 			_id = 0;
264 		}
265 	}
266 
267 	/// Checks if Texture.id is more than 0.
268 	public @property bool valid()
269 	{
270 		return _id > 0;
271 	}
272 }