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