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.color;
11 import bindbc.opengl;
12 import bindbc.opengl.gl;
13 import polyplex.utils;
14 import polyplex.math;
15 import polyplex.utils.mathutils;
16 
17 import std.stdio;
18 
19 /// Exception messages.
20 private enum : string {
21 	ErrorAlreadyStarted		= "SpriteBatch.Begin called more than once! Remember to end spritebatch sessions before beginning new ones.",
22 	ErrorNotStarted			= "SpriteBatch.Begin must be called before SpriteBatch.End.",
23 	ErrorNullTexture		= "Texture was null, please instantiate the texture before using it."
24 }
25 
26 private struct SprBatchData {
27 	Vector2 position;
28 	Vector2 texCoord;
29 	Vector4 color;
30 }
31 
32 private alias SBatchVBO = VertexBuffer!(SprBatchData, Layout.Interleaved);
33 
34 public class GlSpriteBatch : SpriteBatch {
35 private:
36 
37 	// Creation info
38 	enum UniformProjectionName = "PROJECTION";
39 	enum DefaultVert = import("sprite_batch.vsh");
40 	enum DefaultFrag = import("sprite_batch.fsh");
41 	static Shader defaultShader;
42 	static Camera defaultCamera;
43 	static bool hasInitCompleted;
44 
45 	// State info
46 	int queued;
47 	bool hasBegun;
48 	bool swap;
49 
50 	bool isRenderbuffer;
51 
52 	// Buffer
53 	VertexArray 			elementVertexArray;
54 	Buffer 					elementBuffer;
55 	SprBatchData[6][1000] 	elementArray;
56 
57 	// OpenGL state info.
58 	SpriteSorting sortMode;
59 	Blending blendMode;
60 	Sampling sampleMode;
61 	RasterizerState rasterState;
62 	ProjectionState projectionState;
63 	Shader shader;
64 
65 	// Textures
66 	Texture[] currentTextures;
67 
68 	// Viewport
69 	Matrix4x4 viewProjectionMatrix;
70 
71 	// Functions
72 	void setBlendState(Blending state) {
73 		switch(state) {
74 			case Blending.Additive:
75 				glBlendFunc (GL_SRC_COLOR, GL_ONE);
76 				glBlendFunc (GL_SRC_ALPHA, GL_ONE);
77 				break;
78 			case Blending.AlphaBlend:
79 				glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
80 				glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
81 				break;
82 			case Blending.NonPremultiplied:
83 				glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
84 				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
85 				break;
86 			default:
87 				glBlendFunc(GL_ONE, GL_ZERO);
88 				break;
89 		}
90 	}
91 
92 	void setProjectionState(ProjectionState state) {
93 		projectionState = state;
94 	}
95 
96 	void setRasterizerState(RasterizerState state) {
97 		if (state.ScissorTest) GL.Enable(Capability.ScissorTest);
98 		if (state.MSAA) GL.Enable(Capability.Multisample);
99 		if (state.SlopeScaleBias > 0) GL.Enable(Capability.PolygonOffsetFill); 
100 	}
101 
102 	void resetRasterizerState() {
103 		GL.Disable(Capability.ScissorTest);
104 		GL.Disable(Capability.Multisample);
105 		GL.Disable(Capability.PolygonOffsetFill);
106 	}
107 
108 	void setSamplerState(Sampling state) {
109 		switch(state) {
110 			case state.PointWrap:
111 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.Repeat);
112 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.Repeat);
113 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
114 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
115 				break;
116 			case state.PointClamp:
117 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.ClampToEdge);
118 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.ClampToEdge);
119 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
120 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
121 				break;
122 			case state.PointMirror:
123 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.MirroredRepeat);
124 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.MirroredRepeat);
125 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
126 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
127 				break;
128 			case state.LinearWrap:
129 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.Repeat);
130 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.Repeat);
131 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Linear);
132 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Linear);
133 				break;
134 			case state.LinearClamp:
135 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.ClampToEdge);
136 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.ClampToEdge);
137 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Linear);
138 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Linear);
139 				break;
140 			case state.LinearMirror:
141 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.MirroredRepeat);
142 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.MirroredRepeat);
143 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Linear);
144 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Linear);
145 				break;
146 			default:
147 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapS, GL.Repeat);
148 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.WrapT, GL.Repeat);
149 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest);
150 				GL.SetTextureParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest);
151 				break;
152 		}
153 	}
154 
155 	void addVertex(int offset, float x, float y, float r, float g, float b, float a, float u, float v) {
156 		this.elementArray[queued][offset].position = Vector2(x, y);
157 		this.elementArray[queued][offset].texCoord = Vector2(u, v);
158 		this.elementArray[queued][offset].color = Vector4(r, g, b, a);
159 	}
160 
161 	void checkFlush(Texture[] textures) {
162 
163 		// Throw exception if texture isn't instantiated.
164 		if (textures is null || textures.length == 0) throw new Exception(ErrorNullTexture);
165 
166 		// Flush batch if needed, then update texture.
167 		if (currentTextures != textures || queued+1 >= elementArray.length) {
168 			if (currentTextures !is null || textures.length > 0) Flush();
169 			currentTextures = textures;
170 		}
171 	}
172 
173 	void render() {
174 		// Skip out of nothing to render.
175 		if (queued == 0) return;
176 
177 		// Bind vertex array.
178 		elementVertexArray.Bind();
179 
180 		// Bind buffer
181 		elementBuffer.Bind(BufferType.Vertex);
182 
183 		// Refill data
184 		elementBuffer.SubData(0, SprBatchData.sizeof*(queued*6), elementArray.ptr);
185 
186 		// Setup attribute pointers.
187 		elementVertexArray.EnableArray(0);
188 		elementVertexArray.EnableArray(1);
189 		elementVertexArray.EnableArray(2);
190 		elementVertexArray.AttribPointer(0, 2, GL_FLOAT, GL_FALSE, SprBatchData.sizeof, cast(void*)SprBatchData.position.offsetof);
191 		elementVertexArray.AttribPointer(1, 2, GL_FLOAT, GL_FALSE, SprBatchData.sizeof, cast(void*)SprBatchData.texCoord.offsetof);
192 		elementVertexArray.AttribPointer(2, 4, GL_FLOAT, GL_FALSE, SprBatchData.sizeof, cast(void*)SprBatchData.color.offsetof);
193 
194 		// TODO: Optimize rendering routine.
195 
196 		// Attach everything else and render.
197 		shader.Attach();
198 		if (currentTextures !is null) {
199 			foreach(i, currentTexture; currentTextures) {
200 				currentTexture.Bind(TextureType.Tex2D);
201 				currentTexture.AttachTo(cast(int)i);
202 			}
203 		}
204 		setSamplerState(sampleMode);
205 		setBlendState(blendMode);
206 		shader.SetUniform(shader.GetUniform(UniformProjectionName), MultMatrices);
207 
208 		GL.DrawArrays(GL.Triangles, 0, queued*6);
209 
210 		if (currentTextures !is null) {
211 			foreach(currentTexture; currentTextures) {
212 				currentTexture.Bind(TextureType.Tex2D);
213 				currentTexture.AttachTo(0);
214 			}
215 		}
216 
217 	}
218 	
219 	void draw(int width, int height, Rectangle pos, Rectangle cutout, float rotation, Vector2 origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
220 		float x1, y1;
221 		float x2, y2;
222 		float x3, y3;
223 		float x4, y4;
224 
225 		static import std.math;
226 		float scaleX = cast(float)pos.Width/cast(float)width;
227 		float scaleY = cast(float)pos.Height/cast(float)height;
228 		float cx = origin.X*scaleX;
229 		float cy = origin.Y*scaleY;
230 
231 		if (rotation != 0) {
232 
233 			//top left
234 			float p1x = -cx;
235 			float p1y = -cy;
236 
237 			//top right
238 			float p2x = pos.Width - cx;
239 			float p2y = -cy;
240 
241 			//bottom right
242 			float p3x = pos.Width - cx;
243 			float p3y = pos.Height - cy;
244 
245 			//bottom left
246 			float p4x = -cx;
247 			float p4y = pos.Height - cy;
248 
249 			// Respect OffsetOrigin
250 			if (!OffsetOrigin) {
251 				cx = 0;
252 				cy = 0;
253 			}
254 
255 			//Rotation sine and co-sine
256 			float cos = std.math.cos(rotation);
257 			float sin = std.math.sin(rotation);
258 			x1 = pos.X + (cos * p1x - sin * p1y) + cx;
259 			y1 = pos.Y + (sin * p1x + cos * p1y) + cy;
260 
261 			x2 = pos.X + (cos * p2x - sin * p2y) + cx;
262 			y2 = pos.Y + (sin * p2x + cos * p2y) + cy;
263 
264 			x3 = pos.X + (cos * p3x - sin * p3y) + cx;
265 			y3 = pos.Y + (sin * p3x + cos * p3y) + cy;
266 
267 			x4 = pos.X + (cos * p4x - sin * p4y) + cx;
268 			y4 = pos.Y + (sin * p4x + cos * p4y) + cy;
269 		} else {
270 			// Respect OffsetOrigin
271 			if (OffsetOrigin) {
272 				cx = 0;
273 				cy = 0;
274 			}
275 			x1 = pos.X-cx;
276 			y1 = pos.Y-cy;
277 
278 			x2 = pos.X+pos.Width-cx;
279 			y2 = pos.Y-cy;
280 			
281 			x3 = pos.X+pos.Width-cx;
282 			y3 = pos.Y+pos.Height-cy;
283 
284 			x4 = pos.X-cx;
285 			y4 = pos.Y+pos.Height-cy;
286 		}
287 		// Remove any edges in spritesheets/atlases by cutting a tiiiiiny portion away
288 		float pxx = 0.2f/cast(float)width;
289 		float pxy = 0.2f/cast(float)height;
290 
291 		float u = ((cutout.X)/cast(float)width)+pxx;
292 		float u2 = ((cutout.X+cutout.Width)/cast(float)width)-pxx;
293 		if ((flip&SpriteFlip.FlipVertical)>0) {
294 			float ux = u;
295 			u = u2;
296 			u2 = ux;
297 		}
298 
299 		float v = ((cutout.Y)/cast(float)height)+pxy;
300 		float v2 = ((cutout.Y+cutout.Height)/cast(float)height)-pxy;
301 		if ((flip&SpriteFlip.FlipHorizontal)>0) {
302 			float vx = v;
303 			v = v2;
304 			v2 = vx;
305 		}
306 
307 		// TODO: FIX THIS CURSED CODE.
308 		if (isRenderbuffer) {
309 			float vx = v;
310 			v = v2;
311 			v2 = vx;
312 		}
313 
314 		addVertex(0, x1, y1, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v); // TOP LEFT
315 		addVertex(1, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
316 		addVertex(2, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
317 		addVertex(3, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
318 		addVertex(4, x3, y3, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v2); // BOTTOM RIGHT
319 		addVertex(5, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
320 		queued++;
321 	}
322 
323 public:
324 
325 	this() {
326 		elementVertexArray = new VertexArray();
327 		elementVertexArray.Bind();
328 		elementBuffer = new Buffer();
329 		elementBuffer.Bind(BufferType.Vertex);
330 		elementBuffer.Data(elementArray.sizeof, elementArray.ptr, BufferUsage.DynamicDraw);
331 		Logger.VerboseDebug("Some OpenGL features are not implemented yet, namely DepthStencilState, RasterizerState and some SpriteSortModes.");
332 	}
333 
334 	/// Initialize sprite batch
335 	static void InitializeSpritebatch() {
336 		if (!hasInitCompleted) hasInitCompleted = !hasInitCompleted;
337 		else return;
338 
339 		defaultShader = new GLShader(new ShaderCode(DefaultVert, DefaultFrag));
340 		defaultCamera = new Camera2D(Vector2(0, 0));
341 		defaultCamera.Update();
342 	}
343 
344 	/// Get matrix.
345 	override Matrix4x4 MultMatrices() {
346 		switch(projectionState) {
347 			case ProjectionState.Perspective:
348 				return defaultCamera.ProjectPerspective(Renderer.Window.ClientBounds.Width, 90f, Renderer.Window.ClientBounds.Height) * viewProjectionMatrix;
349 			default:
350 				return defaultCamera.ProjectOrthographic(Renderer.Window.ClientBounds.Width, Renderer.Window.ClientBounds.Height) * viewProjectionMatrix;
351 		}
352 	}
353 
354 	/**
355 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
356 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
357 	*/
358 	override void Begin() {
359 		Begin(SpriteSorting.Deferred, Blending.NonPremultiplied, Sampling.LinearClamp, RasterizerState.Default, defaultShader, defaultCamera);
360 	}
361 
362 	/**
363 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
364 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
365 	*/
366 	override void Begin(SpriteSorting sortMode, Blending blendState, Sampling sampleState, RasterizerState rasterState, Shader shader, Matrix4x4 matrix) {
367 		if (hasBegun) throw new Exception(ErrorAlreadyStarted);
368 		hasBegun = true;
369 		this.sortMode = sortMode;
370 		this.blendMode = blendState;
371 		this.sampleMode = sampleState;
372 		this.blendMode = blendState;
373 		this.viewProjectionMatrix = matrix;
374 		this.shader = shader;
375 
376 		this.rasterState = rasterState;
377 		setRasterizerState(rasterState);
378 
379 		this.shader = shader !is null ? shader : defaultShader;
380 		this.currentTextures = null;
381 		this.queued = 0;
382 	}
383 
384 	/**
385 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
386 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
387 	*/
388 	override void Begin(SpriteSorting sortMode, Blending blendState, Sampling sampleState, RasterizerState rasterState, Shader shader, Camera camera) {
389 		Camera cam = camera !is null ? camera : defaultCamera;
390 		cam.Update();
391 		Begin(sortMode, blendState, sampleState, rasterState, shader, cam.Matrix);
392 	}
393 
394 	override void Begin(SpriteSorting sortMode, Blending blendState, Sampling sampleState, RasterizerState rasterState, ProjectionState pstate, Shader shader, Camera camera) {
395 		setProjectionState(pstate);
396 		Begin(sortMode, blendState, sampleState, rasterState, shader, camera);
397 	}
398 
399 	override void Draw(Texture2D texture, Rectangle pos, Rectangle cutout, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
400 		Draw(texture, pos, cutout, 0f, Vector2(-1, -1), color, flip, zlayer);
401 	}
402 
403 	override void Draw(Texture2D texture, Rectangle pos, Rectangle cutout, float rotation, Vector2 origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
404 		isRenderbuffer = false;
405 		checkFlush([(cast(GlTexture2D)texture).GLTexture]);
406 		draw(texture.Width, texture.Height, pos, cutout, rotation, origin, color, flip, zlayer);
407 	}
408 
409 	override void Draw(polyplex.core.render.Framebuffer buffer, Rectangle pos, Rectangle cutout, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
410 		Draw(buffer, pos, cutout, 0f, Vector2(-1, -1), color, flip, zlayer);
411 	}
412 
413 	override void Draw(polyplex.core.render.Framebuffer buffer, Rectangle pos, Rectangle cutout, float rotation, Vector2 origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
414 		isRenderbuffer = true;
415 		checkFlush((cast(GlFramebufferImpl)buffer.Implementation).OutTextures);
416 		draw(buffer.Width, buffer.Height, pos, cutout, rotation, origin, color, flip, zlayer);
417 	}
418 
419 	override void DrawAABB(Texture2D texture, Rectangle pos_top, Rectangle pos_bottom, Rectangle cutout, Vector2 Origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
420 		checkFlush([(cast(GlTexture2D)texture).GLTexture]);
421 		float x1, y1;
422 		float x2, y2;
423 		float x3, y3;
424 		float x4, y4;
425 
426 		static import std.math;
427 		x1 = pos_top.X;
428 		y1 = pos_top.Y;
429 
430 		x2 = pos_top.Width;
431 		y2 = pos_top.Height;
432 		
433 		x3 = pos_bottom.Width;
434 		y3 = pos_bottom.Height;
435 
436 		x4 = pos_bottom.X;
437 		y4 = pos_bottom.Y;
438 		float pxx = 0.2f/cast(float)texture.Width;
439 		float pxy = 0.2f/cast(float)texture.Height;
440 
441 		float u = ((cutout.X)/cast(float)texture.Width)+pxx;
442 		float u2 = ((cutout.X+cutout.Width)/cast(float)texture.Width)-pxx;
443 		if ((flip&SpriteFlip.FlipVertical)>0) {
444 			float ux = u;
445 			u = u2;
446 			u2 = ux;
447 		}
448 
449 		float v = ((cutout.Y)/cast(float)texture.Height)+pxy;
450 		float v2 = ((cutout.Y+cutout.Height)/cast(float)texture.Height)-pxy;
451 		if ((flip&SpriteFlip.FlipHorizontal)>0) {
452 			float vx = v;
453 			v = v2;
454 			v2 = vx;
455 		}
456 
457 		addVertex(0, x1, y1, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v); // TOP LEFT
458 		addVertex(1, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
459 		addVertex(2, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
460 		addVertex(3, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
461 		addVertex(4, x3, y3, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v2); // BOTTOM RIGHT
462 		addVertex(5, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
463 		queued++;
464 	}
465 	
466 	override void Flush() {
467 		render();
468 		currentTextures = [];
469 		queued = 0;
470 	}
471 
472 	override void SwapChain() {
473 
474 	}
475 
476 	override void End() {
477 		hasBegun = false;
478 		Flush();
479 		resetRasterizerState();
480 	}
481 }