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 	public static bool supportsAnisotropy;
82 
83 	public static void load()
84 	{
85 		_white = new Texture();
86 		_white.create(1, 1, cast(ubyte[])[255, 255, 255, 255]);
87 	}
88 
89 	/// Only allocates memory for the instance but does not create anything.
90 	public this() {}
91 
92 	/// Creates and loads the texture with the given filters.
93 	version (BindSDL_Image) public this(string file, TextureFilterMode min = TextureFilterMode.Linear, TextureFilterMode mag = TextureFilterMode.Linear, TextureClampMode wrapX = TextureClampMode.Repeat, TextureClampMode wrapY = TextureClampMode.Repeat)
94 	{
95 		if (min == TextureFilterMode.LinearMipmapLinear || min == TextureFilterMode.LinearMipmapNearest)
96 			enableMipMaps = true;
97 		minFilter = min;
98 		magFilter = mag;
99 		this.wrapX = wrapX;
100 		this.wrapY = wrapY;
101 		fromBitmap(Bitmap.load(file));
102 	}
103 
104 	public ~this()
105 	{
106 		dispose();
107 	}
108 
109 	/// Creates a width x height texture containing the pixel data in RGBA ubyte format.
110 	public void create(uint width, uint height, void[] pixels)
111 	{
112 		create(width, height, GL_RGBA, pixels);
113 	}
114 
115 	/// Recreates a width x height texture containing the pixel data in RGBA ubyte format without disposing the old one.
116 	public void recreate(uint width, uint height, void[] pixels)
117 	{
118 		recreate(width, height, GL_RGBA, pixels);
119 	}
120 
121 	/// Creates a width x height texture containing the pixel data using ubytes.
122 	public void create(uint width, uint height, int mode, void[] pixels)
123 	{
124 		glGenTextures(1, &_id);
125 		glBindTexture(GL_TEXTURE_2D, _id);
126 
127 		glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
128 
129 		applyParameters();
130 
131 		this.inMode = mode;
132 		this.mode = mode;
133 		_width = width;
134 		_height = height;
135 
136 		if (!valid)
137 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
138 	}
139 
140 	/// Recreates a width x height texture containing the pixel data using ubytes without disposing the old one.
141 	public void recreate(uint width, uint height, int mode, void[] pixels)
142 	{
143 		bind(0);
144 		glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
145 
146 		applyParameters();
147 
148 		this.inMode = mode;
149 		this.mode = mode;
150 		_width = width;
151 		_height = height;
152 
153 		if (!valid)
154 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
155 	}
156 
157 	/// Creates a width x height texture containing the pixel data in `inMode` format using `type` as array type and internally convertes to `mode`.
158 	public void create(uint width, uint height, int inMode, int mode, void[] pixels, int type = GL_UNSIGNED_BYTE)
159 	{
160 		glGenTextures(1, &_id);
161 		glBindTexture(GL_TEXTURE_2D, _id);
162 
163 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, type, pixels.ptr);
164 
165 		applyParameters();
166 
167 		this.inMode = inMode;
168 		this.mode = mode;
169 		_width = width;
170 		_height = height;
171 
172 		if (!valid)
173 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
174 	}
175 
176 	/// 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.
177 	public void recreate(uint width, uint height, int inMode, int mode, void[] pixels, int type = GL_UNSIGNED_BYTE)
178 	{
179 		bind(0);
180 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, type, pixels.ptr);
181 
182 		applyParameters();
183 
184 		this.inMode = inMode;
185 		this.mode = mode;
186 		_width = width;
187 		_height = height;
188 
189 		if (!valid)
190 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
191 	}
192 
193 	/// Use this when changing filter or wrap after creating the texture.
194 	/// Calls automatically after `create`.
195 	public void applyParameters()
196 	{
197 		bind(0);
198 
199 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
200 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
201 
202 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapX);
203 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapY);
204 
205 		if (enableMipMaps)
206 		{
207 			glGenerateMipmap(GL_TEXTURE_2D);
208 			if (supportsAnisotropy)
209 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, 16);
210 		}
211 	}
212 
213 	/// Binds the current texture to the given unit.
214 	public void bind(uint unit = 0)
215 	{
216 		glActiveTexture(GL_TEXTURE0 + unit);
217 		glBindTexture(GL_TEXTURE_2D, _id);
218 	}
219 
220 	/// Creates the texture from a bitmap.
221 	public void fromBitmap(Bitmap bitmap, string name = "Bitmap")
222 	{
223 		if (!bitmap.valid)
224 			throw new Exception(name ~ " is invalid!");
225 
226 		int mode = GL_RGB;
227 
228 		if (bitmap.surface.format.BytesPerPixel == 4)
229 		{
230 			mode = GL_RGBA;
231 		}
232 
233 		create(bitmap.width, bitmap.height, mode, bitmap.surface.pixels[0 .. bitmap.width * bitmap.height * bitmap.surface.format.BytesPerPixel]);
234 	}
235 
236 	/// Creates the texture from a bitmap without disposing the old one.
237 	public void recreateFromBitmap(Bitmap bitmap, string name = "Bitmap")
238 	{
239 		if (!bitmap.valid)
240 			throw new Exception(name ~ " is invalid!");
241 
242 		int mode = GL_RGB;
243 
244 		if (bitmap.surface.format.BytesPerPixel == 4)
245 		{
246 			mode = GL_RGBA;
247 		}
248 
249 		recreate(bitmap.width, bitmap.height, mode, bitmap.surface.pixels[0 .. bitmap.width * bitmap.height * bitmap.surface.format.BytesPerPixel]);
250 	}
251 
252 	/// Resizes the texture containing the new data.
253 	public void resize(uint width, uint height, void[] pixels = null)
254 	{
255 		bind(0);
256 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
257 		_width = width;
258 		_height = height;
259 	}
260 
261 	/// Deletes the texture and invalidates `this`.
262 	public void dispose()
263 	{
264 		if (valid)
265 		{
266 			glDeleteTextures(1, &_id);
267 			_id = 0;
268 		}
269 	}
270 
271 	/// Checks if Texture.id is more than 0.
272 	public @property bool valid()
273 	{
274 		return _id > 0;
275 	}
276 }