1 module polyplex.core.render.gl.batch;
2 import polyplex.core.render;
3 import polyplex.core.render.gl.shader;
4 import polyplex.core.render.gl.gloo;
5 import polyplex.core.render.gl.objects;
6 import polyplex.core.render.gl.renderbuf;
7 import polyplex.core.render.camera;
8 import polyplex.core.content.textures;
9 import polyplex.core.content.gl.textures;
10 import polyplex.core.content.font;
11 import polyplex.core.color;
12 import bindbc.opengl;
13 import bindbc.opengl.gl;
14 import polyplex.utils;
15 import polyplex.math;
16 import polyplex.utils.mathutils;
17 import std.utf;
18 
19 import std.stdio;
20 
21 /// Exception messages.
22 private enum : string {
23 	ErrorAlreadyStarted		= "SpriteBatch.Begin called more than once! Remember to end spritebatch sessions before beginning new ones.",
24 	ErrorNotStarted			= "SpriteBatch.Begin must be called before SpriteBatch.End.",
25 	ErrorNullTexture		= "Texture was null, please instantiate the texture before using it."
26 }
27 
28 private struct SprBatchData {
29 	Vector3 position;
30 	Vector2 texCoord;
31 	Vector4 color;
32 }
33 
34 private alias SBatchVBO = VertexBuffer!(SprBatchData, Layout.Interleaved);
35 
36 public class SpriteBatch {
37 private:
38 
39 	// Creation info
40 	enum UniformProjectionName = "PROJECTION";
41 	enum DefaultVert = import("sprite_batch.vert");
42 	enum DefaultFrag = import("sprite_batch.frag");
43 	enum DefaultVertFont = import("font_batch.vert");
44 	enum DefaultFragFont = import("font_batch.frag");
45 	static Shader defaultShader;
46 	static Shader defaultShaderFont;
47 	static Camera defaultCamera;
48 	static bool hasInitCompleted;
49 
50 	// Previous shader, used in font rendering
51 	Shader prevShader;
52 
53 	// State info
54 	int queued;
55 	bool hasBegun;
56 	bool swap;
57 
58 	bool isRenderbuffer;
59 
60 	// Buffer
61 	VertexArray 				elementVertexArray;
62 	Buffer 						elementBuffer;
63 	SprBatchData[6][16_384] 	elementArray;
64 
65 	// OpenGL state info.
66 	SpriteSorting sortMode;
67 	Blending blendMode;
68 	Sampling sampleMode;
69 	RasterizerState rasterState;
70 	ProjectionState projectionState;
71 	Shader shader;
72 
73 	// Textures
74 	Texture[] currentTextures;
75 
76 	// Viewport
77 	Matrix4x4 viewProjectionMatrix;
78 
79 	// Functions
80 	void setBlendState(Blending state) {
81 		switch(state) {
82 			case Blending.Additive:
83 				GL.BlendFunc(GL.SourceAlpha, GL.One);
84 				GL.BlendFunc(GL.SourceAlpha, GL.One);
85 				break;
86 			case Blending.AlphaBlend:
87 				GL.BlendFunc(GL.One, GL.OneMinusSourceColor);
88 				GL.BlendFunc(GL.One, GL.OneMinusSourceAlpha);
89 				break;
90 			case Blending.NonPremultiplied:
91 				GL.BlendFunc(GL.SourceColor, GL.OneMinusSourceColor);
92 				GL.BlendFunc(GL.SourceAlpha, GL.OneMinusSourceAlpha);
93 				break;
94 			default:
95 				GL.BlendFunc(GL.One, GL.Zero);
96 				break;
97 		}
98 	}
99 
100 	void setProjectionState(ProjectionState state) {
101 		projectionState = state;
102 	}
103 
104 	void setRasterizerState(RasterizerState state) {
105 		if (state.ScissorTest) GL.Enable(Capability.ScissorTest);
106 		if (state.MSAA) GL.Enable(Capability.Multisample);
107 		if (state.SlopeScaleBias > 0) GL.Enable(Capability.PolygonOffsetFill); 
108 
109 		if (state.DepthTest) {
110 			GL.Enable(Capability.DepthTesting);
111 			GL.DepthFunc(GL.Less);
112 		}
113 
114 		if (state.BackfaceCulling) {
115 			GL.Enable(Capability.CullFace);
116 			GL.CullFace(GL.Front);
117 		}
118 	}
119 
120 	void resetRasterizerState() {
121 		GL.Disable(Capability.ScissorTest);
122 		GL.Disable(Capability.Multisample);
123 		GL.Disable(Capability.PolygonOffsetFill);
124 		GL.Disable(Capability.DepthTesting);
125 		GL.Disable(Capability.CullFace);
126 	}
127 
128 	void setSamplerState(Sampling state) {
129 		switch(state) {
130 			case state.PointWrap:
131 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.Repeat);
132 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.Repeat);
133 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
134 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
135 				break;
136 			case state.PointClamp:
137 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.ClampToEdge);
138 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.ClampToEdge);
139 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
140 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
141 				break;
142 			case state.PointMirror:
143 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.MirroredRepeat);
144 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.MirroredRepeat);
145 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
146 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
147 				break;
148 			case state.LinearWrap:
149 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.Repeat);
150 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.Repeat);
151 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Linear);
152 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Linear);
153 				break;
154 			case state.LinearClamp:
155 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.ClampToEdge);
156 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.ClampToEdge);
157 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Linear);
158 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Linear);
159 				break;
160 			case state.LinearMirror:
161 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.MirroredRepeat);
162 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.MirroredRepeat);
163 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Linear);
164 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Linear);
165 				break;
166 			default:
167 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.Repeat);
168 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.Repeat);
169 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
170 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
171 				break;
172 		}
173 	}
174 
175 	void addVertex(int offset, float x, float y, float r, float g, float b, float a, float u, float v, float depth) {
176 		this.elementArray[queued][offset].position = Vector3(x, y, depth);
177 		this.elementArray[queued][offset].texCoord = Vector2(u, v);
178 		this.elementArray[queued][offset].color = Vector4(r, g, b, a);
179 	}
180 
181 	bool checkFlush(Texture[] textures) {
182 
183 		// Throw exception if texture isn't instantiated.
184 		if (textures is null || textures.length == 0) throw new Exception(ErrorNullTexture);
185 
186 		// Flush batch if needed, then update texture.
187 		if (currentTextures != textures || queued+1 >= elementArray.length) {
188 			if (currentTextures !is null || textures.length > 0) Flush();
189 			currentTextures = textures;
190 			return true;
191 		}
192 		return false;
193 	}
194 
195 	void render() {
196 		// Skip out of nothing to render.
197 		if (queued == 0) return;
198 
199 		// Bind vertex array.
200 		elementVertexArray.Bind();
201 
202 		// Bind buffer
203 		elementBuffer.Bind(BufferType.Vertex);
204 
205 		// Refill data
206 		elementBuffer.SubData(0, SprBatchData.sizeof*(queued*6), elementArray.ptr);
207 
208 		// Setup attribute pointers.
209 		elementVertexArray.EnableArray(0);
210 		elementVertexArray.EnableArray(1);
211 		elementVertexArray.EnableArray(2);
212 		elementVertexArray.AttribPointer(0, 3, GL_FLOAT, GL_FALSE, SprBatchData.sizeof, cast(void*)SprBatchData.position.offsetof);
213 		elementVertexArray.AttribPointer(1, 2, GL_FLOAT, GL_FALSE, SprBatchData.sizeof, cast(void*)SprBatchData.texCoord.offsetof);
214 		elementVertexArray.AttribPointer(2, 4, GL_FLOAT, GL_FALSE, SprBatchData.sizeof, cast(void*)SprBatchData.color.offsetof);
215 
216 		// TODO: Optimize rendering routine.
217 
218 		// Attach everything else and render.
219 		shader.Attach();
220 		if (currentTextures !is null) {
221 			foreach(i, currentTexture; currentTextures) {
222 				currentTexture.Bind(TextureType.Tex2D);
223 				currentTexture.AttachTo(cast(int)i);
224 			}
225 		}
226 		setSamplerState(sampleMode);
227 		setBlendState(blendMode);
228 
229 		glEnable(GL_BLEND);
230 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
231 		shader.SetUniform(shader.GetUniform(UniformProjectionName), viewProjectionMatrix);
232 
233 		GL.DrawArrays(GL.Triangles, 0, queued*6);
234 
235 		if (currentTextures !is null) {
236 			foreach(currentTexture; currentTextures) {
237 				currentTexture.Bind(TextureType.Tex2D);
238 				currentTexture.AttachTo(0);
239 			}
240 		}
241 
242 	}
243 	
244 	void draw(T, Y)(int width, int height, T pos, Y cutout, float rotation, Vector2 origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) if (IsRectangleT!T && IsRectangleT!Y) {
245 		float x1, y1;
246 		float x2, y2;
247 		float x3, y3;
248 		float x4, y4;
249 
250 		static import std.math;
251 		float scaleX = cast(float)pos.Width/cast(float)width;
252 		float scaleY = cast(float)pos.Height/cast(float)height;
253 		float cx = origin.X*scaleX;
254 		float cy = origin.Y*scaleY;
255 
256 		if (rotation != 0) {
257 
258 			//top left
259 			float p1x = -cx;
260 			float p1y = -cy;
261 
262 			//top right
263 			float p2x = pos.Width - cx;
264 			float p2y = -cy;
265 
266 			//bottom right
267 			float p3x = pos.Width - cx;
268 			float p3y = pos.Height - cy;
269 
270 			//bottom left
271 			float p4x = -cx;
272 			float p4y = pos.Height - cy;
273 
274 			// Respect OffsetOrigin
275 
276 			// TODO: Make OffsetOrigina a thing again?
277 			// if (!OffsetOrigin) {
278 			// }
279 			cx = 0;
280 			cy = 0;
281 
282 			//Rotation sine and co-sine
283 			float cos = std.math.cos(rotation);
284 			float sin = std.math.sin(rotation);
285 			x1 = pos.X + (cos * p1x - sin * p1y) + cx;
286 			y1 = pos.Y + (sin * p1x + cos * p1y) + cy;
287 
288 			x2 = pos.X + (cos * p2x - sin * p2y) + cx;
289 			y2 = pos.Y + (sin * p2x + cos * p2y) + cy;
290 
291 			x3 = pos.X + (cos * p3x - sin * p3y) + cx;
292 			y3 = pos.Y + (sin * p3x + cos * p3y) + cy;
293 
294 			x4 = pos.X + (cos * p4x - sin * p4y) + cx;
295 			y4 = pos.Y + (sin * p4x + cos * p4y) + cy;
296 		} else {
297 			// Respect OffsetOrigin
298 
299 			// TODO: Make OffsetOrigin a thing again?
300 			// if (OffsetOrigin) {
301 			// }
302 			cx = 0;
303 			cy = 0;
304 			
305 			x1 = pos.X-cx;
306 			y1 = pos.Y-cy;
307 
308 			x2 = pos.X+pos.Width-cx;
309 			y2 = pos.Y-cy;
310 			
311 			x3 = pos.X+pos.Width-cx;
312 			y3 = pos.Y+pos.Height-cy;
313 
314 			x4 = pos.X-cx;
315 			y4 = pos.Y+pos.Height-cy;
316 		}
317 		// Remove any edges in spritesheets/atlases by cutting a tiiiiiny portion away
318 		float pxx = 0.2f/cast(float)width;
319 		float pxy = 0.2f/cast(float)height;
320 
321 		float u = ((cutout.X)/cast(float)width)+pxx;
322 		float u2 = ((cutout.X+cutout.Width)/cast(float)width)-pxx;
323 		if ((flip&SpriteFlip.FlipVertical)>0) {
324 			float ux = u;
325 			u = u2;
326 			u2 = ux;
327 		}
328 
329 		float v = ((cutout.Y)/cast(float)height)+pxy;
330 		float v2 = ((cutout.Y+cutout.Height)/cast(float)height)-pxy;
331 		if ((flip&SpriteFlip.FlipHorizontal)>0) {
332 			float vx = v;
333 			v = v2;
334 			v2 = vx;
335 		}
336 
337 		// TODO: FIX THIS CURSED CODE.
338 		if (isRenderbuffer) {
339 			float vx = v;
340 			v = v2;
341 			v2 = vx;
342 		}
343 
344 		addVertex(0, x1, y1, color.Rf(), color.Gf(), color.Bf(), color.Af(), u,   v, -zlayer); // TOP LEFT
345 		addVertex(1, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2,  v, -zlayer), // TOP RIGHT
346 		addVertex(2, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u,  v2, -zlayer); // BOTTOM LEFT
347 		addVertex(3, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2,  v, -zlayer), // TOP RIGHT
348 		addVertex(4, x3, y3, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v2, -zlayer); // BOTTOM RIGHT
349 		addVertex(5, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u,  v2, -zlayer); // BOTTOM LEFT
350 		queued++;
351 	}
352 
353 	/// Get matrix.
354 	Matrix4x4 defaultMultMatrices(Matrix4x4 mvpMatrix) {
355 		switch(projectionState) {
356 			case ProjectionState.Perspective:
357 				return defaultCamera.ProjectPerspective(Renderer.Window.ClientBounds.Width, 90f, Renderer.Window.ClientBounds.Height) * mvpMatrix;
358 			default:
359 				return defaultCamera.ProjectOrthographic(Renderer.Window.ClientBounds.Width, Renderer.Window.ClientBounds.Height) * mvpMatrix;
360 		}
361 	}
362 
363 public:
364 
365 	this() {
366 		elementVertexArray = new VertexArray();
367 		elementVertexArray.Bind();
368 		elementBuffer = new Buffer();
369 		elementBuffer.Bind(BufferType.Vertex);
370 		elementBuffer.Data(elementArray.sizeof, elementArray.ptr, BufferUsage.DynamicDraw);
371 		Logger.Warn("Some OpenGL features are not implemented yet, namely DepthStencilState, RasterizerState and some SpriteSortModes.");
372 	}
373 
374 	/// Initialize sprite batch
375 	static void InitializeSpritebatch() {
376 		if (!hasInitCompleted) hasInitCompleted = !hasInitCompleted;
377 		else return;
378 
379 		defaultShader = new Shader(new ShaderCode(DefaultVert, DefaultFrag));
380 		defaultShaderFont = new Shader(new ShaderCode(DefaultVertFont, DefaultFragFont));
381 		defaultCamera = new Camera2D(Vector2(0, 0));
382 		defaultCamera.Update();
383 	}
384 
385 	/**
386 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
387 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
388 	*/
389 	void Begin() {
390 		Begin(SpriteSorting.Deferred, Blending.NonPremultiplied, Sampling.LinearClamp, RasterizerState.Default, defaultShader, defaultCamera);
391 	}
392 
393 	/**
394 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
395 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
396 	*/
397 	void Begin(SpriteSorting sortMode, Blending blendState, Sampling sampleState, RasterizerState rasterState, Shader shader, Matrix4x4 matrix) {
398 		if (hasBegun) throw new Exception(ErrorAlreadyStarted);
399 		hasBegun = true;
400 		this.sortMode = sortMode;
401 		this.blendMode = blendState;
402 		this.sampleMode = sampleState;
403 		this.blendMode = blendState;
404 		this.viewProjectionMatrix = matrix;
405 		this.shader = shader;
406 
407 		this.rasterState = rasterState;
408 		setRasterizerState(rasterState);
409 
410 		this.shader = shader !is null ? shader : defaultShader;
411 		this.currentTextures = null;
412 		this.queued = 0;
413 	}
414 
415 	/**
416 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
417 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
418 	*/
419 	void Begin(SpriteSorting sortMode, Blending blendState, Sampling sampleState, RasterizerState rasterState, Shader shader, Camera camera) {
420 		Camera cam = camera !is null ? camera : defaultCamera;
421 		cam.Update();
422 		Begin(sortMode, blendState, sampleState, rasterState, shader, defaultMultMatrices(cam.Matrix));
423 	}
424 
425 	void Begin(SpriteSorting sortMode, Blending blendState, Sampling sampleState, RasterizerState rasterState, ProjectionState pstate, Shader shader, Camera camera) {
426 		setProjectionState(pstate);
427 		Begin(sortMode, blendState, sampleState, rasterState, shader, camera);
428 	}
429 
430 	void Draw(T, Y)(Texture2D texture, T pos, Y cutout, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) if (IsRectangleT!T && IsRectangleT!Y) {
431 		Draw(texture, pos, cutout, 0f, Vector2(-1, -1), color, flip, zlayer);
432 	}
433 
434 	void Draw(T, Y)(Texture2D texture, T pos, Y cutout, float rotation, Vector2 origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) if (IsRectangleT!T && IsRectangleT!Y) {
435 		isRenderbuffer = false;
436 		checkFlush([(cast(GlTexture2D)texture).GLTexture]);
437 		draw(texture.Width, texture.Height, pos, cutout, rotation, origin, color, flip, zlayer);
438 	}
439 
440 	void Draw(T, Y)(polyplex.core.render.Framebuffer buffer, T pos, Y cutout, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) if (IsRectangleT!T && IsRectangleT!Y) {
441 		Draw(buffer, pos, cutout, 0f, Vector2(-1, -1), color, flip, zlayer);
442 	}
443 
444 	void Draw(T, Y)(polyplex.core.render.Framebuffer buffer, T pos, Y cutout, float rotation, Vector2 origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) if (IsRectangleT!T && IsRectangleT!Y) {
445 		isRenderbuffer = true;
446 		checkFlush(buffer.OutTextures);
447 		draw(buffer.Width, buffer.Height, pos, cutout, rotation, origin, color, flip, zlayer);
448 	}
449 
450 	/**
451 		Begin string rendering
452 	*/
453 	void BeginString(SpriteFont font, Shader textShader = null) {
454 		isRenderbuffer = false;
455 		prevShader = this.shader;
456 
457 		// Support custom font shaders.
458 		checkFlush([(cast(GlTexture2DImpl!(GL_RED, 1))font.getTexture()).GLTexture]);
459 		this.shader = textShader !is null ? textShader : defaultShaderFont;
460 	}
461 
462 	/**
463 		End string rendering
464 	*/
465 	void EndString() {
466 		Flush();
467 
468 		// Revert back to the previous shader.
469 		this.shader = prevShader;
470 	}
471 
472 	/**
473 		Draws a single character, returns the spot for the next character to be drawn
474 
475 		Notice: You'll have to execute BeginString before rendering a character and EndString after you're done rendering characters
476 		Otherwise the wrong shaders will be used, etc.
477 	*/
478 	Vector2 DrawChar(SpriteFont font, dchar character, Vector2 position, Color color, float scale = 1, float zlayer = 0f) {
479 
480 		auto info = font[character];
481 		if (info is null) return Vector2.NaN;
482 
483 		// Position to draw
484 		float posX = cast(int)(position.X + info.bearing.x * scale);
485 		float posY = cast(int)(((position.Y-info.size.y) + cast(int)(info.size.y - info.bearing.y)+font.BaseCharSize.Y)*scale);
486 		Rectangle currentRectangle = Rectangle(
487 			posX, 
488 			posY,
489 			info.size.x*scale, 
490 			info.size.y*scale);
491 
492 		// Clipping for character
493 		Rectangle clipRectangle = Rectangle(info.origin.x, info.origin.y, info.size.x, info.size.y);
494 
495 		// Send to rendering
496 		draw(font.TexSize.X, font.TexSize.Y, currentRectangle, clipRectangle, 0f, Vector2(0, 0), color, SpriteFlip.None, zlayer);
497 
498 		// Bitshift 6 times to get value in pixels
499 		return Vector2(position.X+((info.advance.x >> 6) * scale), position.Y);
500 	}
501 
502 	/**
503 		Draws a string
504 
505 		Allows special formatting rules:
506 			[c=(HEX)] to change the color
507 			[c=clear] to clear colors
508 	*/
509 	void DrawString(SpriteFont font, dstring text, Vector2 position, Color color, float scale = 1f, float zlayer = 0f, Shader textShader = null) {
510 		import std.conv : to;
511 		Color startColor = color;
512 		Color currentColor = startColor;
513 
514 		BeginString(font, textShader);
515 
516 			Vector2 next = position;
517 			size_t i = 0;
518 			size_t line = 0;
519 			while(i < text.length) {
520 
521 				if (text[i] == '\n') {
522 					line++;
523 					i++;
524 					next.X = position.X;
525 					next.Y += font.BaseCharSize.Y+ (font.BaseCharSize.Y/2);
526 					continue;
527 				}
528 
529 				if (i < text.length-4) {
530 
531 					// Check if we're in a formatting rule
532 					if (text[i] == '[' && text[i+2] == '=') {
533 						switch(text[i+1]) {
534 							// Color formatting rule
535 							case 'c':
536 								// Move the 4 characters of the formatting rule on
537 								i += 3;
538 								string fmtColorStr;
539 
540 								// Fetch all the assignment data untill we hit the end
541 								while (text[i] != ']') {
542 									fmtColorStr ~= text[i++];
543 								}
544 
545 								// Skip the last closing bracket
546 								i++;
547 								
548 								// If the command was to clear color, clear it.
549 								if (fmtColorStr == "clear") {
550 									currentColor = startColor;
551 									break;
552 								}
553 
554 								// Break out if the color data length isn't an even number
555 								if (fmtColorStr.length % 2 != 0) break;
556 
557 								// pushback value to allow shorter hex values
558 								size_t pushBack = 8*(3-((fmtColorStr.length/2)-1));
559 
560 								// The output color value, if no alpha was chosen then automatically insert 255.
561 								uint colorValue = ((fmtColorStr.length/2) < 4 ? 0x000000FF : 0x00) | (fmtColorStr.to!uint(16) << pushBack);
562 
563 								currentColor = new Color(colorValue);
564 								break;
565 
566 							// Wasn't a formatting rule, nevermind
567 							default: break;
568 						}
569 					}
570 				}
571 
572 				// Draw a character
573 				dchar c = cast(dchar)text[i];
574 				next = DrawChar(font, c, next, currentColor, scale, zlayer);
575 
576 				// Next character
577 				i++;
578 			}
579 
580 		EndString();
581 	}
582 
583 	/**
584 		Draws a string
585 
586 		Allows special formatting rules:
587 			[c=(HEX)] to change the color
588 			[c=clear] to clear colors
589 	*/
590 	void DrawString(SpriteFont font, string utf8text, Vector2 position, Color color, float scale = 1f, float zlayer = 0f, Shader textShader = null) {
591 		dstring text = toUTF32(utf8text);
592 		DrawString(font, text, position, color, scale, zlayer, shader);
593 	}
594 
595 	/**
596 		Draws a string
597 
598 		Allows special formatting rules:
599 			[c=(HEX)] to change the color
600 			[c=clear] to clear colors
601 	*/
602 	void DrawString(SpriteFont font, wstring utf16text, Vector2 position, Color color, float scale = 1f, float zlayer = 0f, Shader textShader = null) {
603 		dstring text = toUTF32(utf16text);
604 		DrawString(font, text, position, color, scale, zlayer, shader);
605 	}
606 	
607 	void Flush() {
608 		render();
609 		currentTextures = [];
610 		queued = 0;
611 	}
612 
613 	void SwapChain() {
614 
615 	}
616 
617 	void End() {
618 		hasBegun = false;
619 		Flush();
620 		resetRasterizerState();
621 	}
622 }