1 module D2DGame.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 	/// Creates a width x height texture containing the pixel data using ubytes.
113 	public void create(uint width, uint height, int mode, void[] pixels)
114 	{
115 		glGenTextures(1, &_id);
116 		glBindTexture(GL_TEXTURE_2D, _id);
117 
118 		glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
119 
120 		applyParameters();
121 
122 		this.inMode = mode;
123 		this.mode	= mode;
124 		_width		= width;
125 		_height		= height;
126 
127 		if (!valid)
128 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
129 	}
130 
131 	/// Creates a width x height texture containing the pixel data in `inMode` format using `type` as array type and internally convertes to `mode`.
132 	public void create(uint width, uint height, int inMode, int mode, void[] pixels, int type = GL_UNSIGNED_BYTE)
133 	{
134 		glGenTextures(1, &_id);
135 		glBindTexture(GL_TEXTURE_2D, _id);
136 
137 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, type, pixels.ptr);
138 
139 		applyParameters();
140 
141 		this.inMode = inMode;
142 		this.mode	= mode;
143 		_width		= width;
144 		_height		= height;
145 
146 		if (!valid)
147 			throw new Exception("OpenGL ErrorCode " ~ to!string(glGetError()));
148 	}
149 
150 	/// Use this when changing filter or wrap after creating the texture.
151 	/// Calls automatically after `create`.
152 	public void applyParameters()
153 	{
154 		bind(0);
155 
156 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
157 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
158 
159 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapX);
160 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapY);
161 
162 		if (enableMipMaps)
163 		{
164 			glGenerateMipmap(GL_TEXTURE_2D);
165 			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
166 		}
167 	}
168 
169 	/// Binds the current texture to the given unit.
170 	public void bind(uint unit)
171 	{
172 		glActiveTexture(GL_TEXTURE0 + unit);
173 		glBindTexture(GL_TEXTURE_2D, _id);
174 	}
175 
176 	/// Creates the texture from a bitmap.
177 	public void fromBitmap(Bitmap bitmap, string name = "Bitmap")
178 	{
179 		if (!bitmap.valid)
180 			throw new Exception(name ~ " is invalid!");
181 
182 		int mode = GL_RGB;
183 
184 		if (bitmap.surface.format.BytesPerPixel == 4)
185 		{
186 			mode = GL_RGBA;
187 		}
188 
189 		create(bitmap.width, bitmap.height, mode, bitmap.surface.pixels[0 .. bitmap.width * bitmap.height * bitmap.surface.format.BytesPerPixel]);
190 	}
191 
192 	/// Resizes the texture containing the new data.
193 	public void resize(uint width, uint height, void[] pixels = null)
194 	{
195 		bind(0);
196 		glTexImage2D(GL_TEXTURE_2D, 0, inMode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels.ptr);
197 		_width	= width;
198 		_height = height;
199 	}
200 
201 	/// Deletes the texture and invalidates `this`.
202 	public void dispose()
203 	{
204 		if (valid)
205 		{
206 			glDeleteTextures(1, &_id);
207 			_id = 0;
208 		}
209 	}
210 
211 	/// Checks if Texture.id is more than 0.
212 	public @property bool valid()
213 	{
214 		return _id > 0;
215 	}
216 }