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.objects;
5 import polyplex.core.render.camera;
6 import polyplex.core.content.textures;
7 import polyplex.core.color;
8 import derelict.opengl;
9 import derelict.opengl.gl;
10 import polyplex.utils;
11 import polyplex.math;
12 import polyplex.utils.mathutils;
13 
14 import std.stdio;
15 
16 
17 private struct SprBatchData {
18 	Vector2 ppPosition;
19 	Vector2 ppTexcoord;
20 	Vector4 ppColor;
21 }
22 
23 private alias SBatchVBO = VertexBuffer!(SprBatchData, Layout.Interleaved);
24 
25 /**
26 	OpenGL implementation of a sprite batcher.
27 */
28 public class GlSpriteBatch : SpriteBatch {
29 	private static string uniform_tex_name = "ppTexture";
30 	private static string uniform_prj_name = "ppProjection";
31 	private static string default_vert;
32 	private static string default_frag;
33 	private static Shader default_shader;
34 	private static Camera default_cam;
35 	private static bool has_init;
36 
37 	private int size;
38 	private int queued;
39 	private int last_queued;
40 
41 	private SprBatchData vector_data;
42 	private VertexBuffer!(SprBatchData, Layout.Interleaved) render_object;
43 	private VertexBuffer!(SprBatchData, Layout.Interleaved) render_object_2;
44 
45 	private Renderer renderer;
46 	private SpriteSorting sort_mode; 
47 	private Blending blend_state;
48 	private Sampling sample_state;
49 	private ProjectionState project_state;
50 	private Shader shader;
51 	private Matrix4x4 view_project;
52 	private Texture2D current_texture;
53 	private Texture2D current_texture_2;
54 	private bool has_begun = false;
55 	private bool swap = false;
56 
57 	this(Renderer renderer, int size = 1000) {
58 		this.renderer = renderer;
59 		InitializeSpritebatch();
60 		this.size = size;
61 		render_object = VertexBuffer!(SprBatchData, Layout.Interleaved)([]);
62 		render_object_2 = VertexBuffer!(SprBatchData, Layout.Interleaved)([]);
63 	}
64 
65 	public static void InitializeSpritebatch() {
66 		if (!has_init) has_init = !has_init;
67 		else return;
68 
69 		default_vert = import("sprite_batch.vsh");
70 		default_frag = import("sprite_batch.fsh");
71 
72 		default_shader = new GLShader(new ShaderCode(default_vert, default_frag, ["ppPosition", "ppTexcoord", "ppColor"]));
73 		default_cam = new Camera2D(Vector2(0, 0));
74 		default_cam.Update();
75 	}
76 
77 	private void set_blend_state(Blending state) {
78 		if (state == Blending.Additive) {
79 			glBlendFunc (GL_SRC_COLOR, GL_ONE);
80 			glBlendFunc (GL_SRC_ALPHA, GL_ONE);
81 			return;
82 		}
83 		if (state == Blending.AlphaBlend) {
84 			glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
85 			glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
86 			return;
87 		}
88 		if (state == Blending.NonPremultiplied) {
89 			glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
90 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
91 			return;
92 		}
93 		glBlendFunc(GL_ONE, GL_ZERO);
94 		Logger.Warn("Some things are not implemented yet, noteably: DepthStencilStates, RasterizerStates and some SpriteSortModes.");
95 	}
96 
97 	private void set_projection_state(ProjectionState state) {
98 		this.project_state = state;
99 	}
100 
101 	private void set_sampler_state(Sampling sampling) {
102 		switch (sampling) {
103 			case sampling.PointWrap:
104 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
105 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
106 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
107 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
108 				break;
109 			case sampling.PointClamp:
110 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
111 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
112 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
113 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
114 				break;
115 			case sampling.LinearWrap:
116 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
117 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
118 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
119 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
120 				break;
121 			case sampling.LinearClamp:
122 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
123 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
124 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
125 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
126 				break;
127 			default:
128 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
129 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
130 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
131 				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
132 				break;
133 		}
134 	}
135 
136 	private Matrix4x4 mult_matrices() {
137 		if (this.project_state == ProjectionState.Perspective)
138 			return this.default_cam.ProjectPerspective(renderer.Window.Width, 90f, renderer.Window.Height) * this.view_project;
139 		return this.default_cam.ProjectOrthographic(renderer.Window.Width, renderer.Window.Height) * this.view_project;
140 	}
141 
142 	public override void Begin() {
143 		Begin(SpriteSorting.Deferred, Blending.AlphaBlend, Sampling.LinearClamp, default_shader, default_cam);
144 	}
145 
146 	/**
147 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
148 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
149 	*/
150 	public override void Begin(SpriteSorting sort_mode, Blending blend_state, Sampling sample_state, ProjectionState pstate, Shader s, Camera camera) {
151 		set_projection_state(pstate);
152 		Begin(sort_mode, blend_state, sample_state, s, camera);
153 	}
154 
155 	/**
156 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
157 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
158 	*/
159 	public override void Begin(SpriteSorting sort_mode, Blending blend_state, Sampling sample_state, Shader s, Camera camera) {
160 		Camera cam = camera;
161 		if (cam is null) cam = default_cam;
162 		cam.Update();
163 		Begin(sort_mode, blend_state, sample_state, s, cam.Matrix);
164 	}
165 
166 	/**
167 		Begin begins the spritebatch, setting up sorting modes, blend states and sampling.
168 		Begin also attaches a custom shader (if chosen) and sets the camera/view matrix.
169 	*/
170 	public override void Begin(SpriteSorting sort_mode, Blending blend_state, Sampling sample_state, Shader s, Matrix4x4 matrix) {
171 		if (this.has_begun) throw new Exception("SpriteBatch.Begin called more than once! Remember to end spritebatch sessions before beginning new ones.");
172 		this.has_begun = true;
173 		this.sort_mode = sort_mode;
174 		this.sample_state = sample_state;
175 		this.blend_state = blend_state;
176 		this.view_project = matrix;
177 		this.shader = s;
178 		if (this.shader is null) this.shader = default_shader;
179 		this.current_texture = null;
180 		this.queued = 0;
181 	}
182 
183 	/**
184 		Flush flushes the spritebatch, drawing whatever that has been batched to the screen.
185 		End() will automatically call this.
186 	*/
187 	public override void Flush() {
188 		render();
189 		this.render_object.Data = [];
190 		last_queued = queued;
191 		queued = 0;
192 	}
193 
194 	private void add_vertex(int offset, float x, float y, float r, float g, float b, float a, float u, float v) {
195 		//Logger.VerboseDebug("{0}, {1} == {2}", offset, (queued*6), (queued*6)+offset);
196 		if ((queued*6)+offset >= this.render_object.Data.length)
197 			this.render_object.Data.length++;
198 		this.render_object.Data[(queued*6)+offset].ppPosition = Vector2(x, y);
199 		this.render_object.Data[(queued*6)+offset].ppColor = Vector4(r, g, b, a);
200 		this.render_object.Data[(queued*6)+offset].ppTexcoord = Vector2(u, v);
201 	}
202 
203 	private void check_flush(Texture2D texture) {
204 		if ((texture is null)) throw new Exception("Null value sprite. Please load a sprite before drawing it.");
205 
206 		if (texture != current_texture || queued > size) {
207 			if (!(current_texture is null)) Flush();
208 			current_texture = texture;
209 		}
210 	}
211 
212 	private void render() {
213 		// Buffer the data
214 		this.render_object.Bind();
215 		if ((queued*6) < last_queued) render_object.UpdateSubData(0, 0, (queued*6));
216 		else render_object.UpdateBuffer();
217 
218 		// Draw current
219 		this.render_object.Bind();
220 
221 		// Attach textures, set states, uniforms, etc.
222 		this.shader.Attach();
223 		if (!(current_texture is null)) current_texture.Bind(0, this.shader);
224 		set_sampler_state(sample_state);
225 		set_blend_state(blend_state);
226 		this.shader.SetUniform(this.shader.GetUniform(uniform_prj_name), mult_matrices());
227 		
228 		// Draw.
229 		render_object.Draw(queued*6);
230 		
231 		this.render_object.Unbind();
232 	}
233 
234 	/**
235 		Swaps the draw chain (double buffering)
236 
237 		TODO: Add double buffering here.
238 	*/
239 	public override void SwapChain() {
240 		swap = !swap;
241 	}
242 
243 	/**
244 		Draw draws a texture.
245 	*/
246 	public override void Draw(Texture2D texture, Rectangle pos, Rectangle cutout, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0f) {
247 		Draw(texture, pos, cutout, 0f, Vector2(-1, -1), color, flip, zlayer);
248 	}
249 
250 	/**
251 		Draw draws a texture.
252 	*/
253 	public override void Draw(Texture2D texture, Rectangle pos, Rectangle cutout, float rotation, Vector2 Origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0) {
254 		check_flush(texture);
255 		float x1, y1;
256 		float x2, y2;
257 		float x3, y3;
258 		float x4, y4;
259 
260 		static import std.math;
261 
262 		if (rotation != 0) {
263 			float scaleX = pos.Width/texture.Width;
264 			float scaleY = pos.Height/texture.Height;
265 			float cx = Origin.X*scaleX;
266 			float cy = Origin.Y*scaleY;
267 
268 			//top left
269 			float p1x = -cx;
270 			float p1y = -cy;
271 
272 			//top right
273 			float p2x = pos.Width - cx;
274 			float p2y = -cy;
275 
276 			//bottom right
277 			float p3x = pos.Width - cx;
278 			float p3y = pos.Height - cy;
279 
280 			//bottom left
281 			float p4x = -cx;
282 			float p4y = pos.Height - cy;
283 
284 			//Rotation sine and co-sine
285 			float cos = std.math.cos(rotation);
286 			float sin = std.math.sin(rotation);
287 
288 			x1 = pos.X + (cos * p1x - sin * p1y) + cx;
289 			y1 = pos.Y + (sin * p1x + cos * p1y) + cy;
290 
291 			x2 = pos.X + (cos * p2x - sin * p2y) + cx;
292 			y2 = pos.Y + (sin * p2x + cos * p2y) + cy;
293 
294 			x3 = pos.X + (cos * p3x - sin * p3y) + cx;
295 			y3 = pos.Y + (sin * p3x + cos * p3y) + cy;
296 
297 			x4 = pos.X + (cos * p4x - sin * p4y) + cx;
298 			y4 = pos.Y + (sin * p4x + cos * p4y) + cy;
299 		} else {
300 			x1 = pos.X;
301 			y1 = pos.Y;
302 
303 			x2 = pos.X+pos.Width;
304 			y2 = pos.Y;
305 			
306 			x3 = pos.X+pos.Width;
307 			y3 = pos.Y+pos.Height;
308 
309 			x4 = pos.X;
310 			y4 = pos.Y+pos.Height;
311 		}
312 		float pxx = 0.2f/cast(float)texture.Width;
313 		float pxy = 0.2f/cast(float)texture.Height;
314 
315 		float u = ((cutout.X)/cast(float)texture.Width)+pxx;
316 		float u2 = ((cutout.X+cutout.Width)/cast(float)texture.Width)-pxx;
317 		if ((flip&SpriteFlip.FlipVertical)>0) {
318 			float ux = u;
319 			u = u2;
320 			u2 = ux;
321 		}
322 
323 		float v = ((cutout.Y)/cast(float)texture.Height)+pxy;
324 		float v2 = ((cutout.Y+cutout.Height)/cast(float)texture.Height)-pxy;
325 		if ((flip&SpriteFlip.FlipHorizontal)>0) {
326 			float vx = v;
327 			v = v2;
328 			v2 = vx;
329 		}
330 
331 		add_vertex(0, x1, y1, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v); // TOP LEFT
332 		add_vertex(1, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
333 		add_vertex(2, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
334 		add_vertex(3, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
335 		add_vertex(4, x3, y3, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v2); // BOTTOM RIGHT
336 		add_vertex(5, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
337 		queued++;
338 	}
339 
340 	/**
341 		Draw draws a texture.
342 		Rectangle pos will act like an AABB rectangle instead.
343 	*/
344 	public override void DrawAABB(Texture2D texture, Rectangle pos_top, Rectangle pos_bottom, Rectangle cutout, Vector2 Origin, Color color, SpriteFlip flip = SpriteFlip.None, float zlayer = 0) {
345 		check_flush(texture);
346 		float x1, y1;
347 		float x2, y2;
348 		float x3, y3;
349 		float x4, y4;
350 
351 		static import std.math;
352 		x1 = pos_top.X;
353 		y1 = pos_top.Y;
354 
355 		x2 = pos_top.Width;
356 		y2 = pos_top.Height;
357 		
358 		x3 = pos_bottom.Width;
359 		y3 = pos_bottom.Height;
360 
361 		x4 = pos_bottom.X;
362 		y4 = pos_bottom.Y;
363 		float pxx = 0.2f/cast(float)texture.Width;
364 		float pxy = 0.2f/cast(float)texture.Height;
365 
366 		float u = ((cutout.X)/cast(float)texture.Width)+pxx;
367 		float u2 = ((cutout.X+cutout.Width)/cast(float)texture.Width)-pxx;
368 		if ((flip&SpriteFlip.FlipVertical)>0) {
369 			float ux = u;
370 			u = u2;
371 			u2 = ux;
372 		}
373 
374 		float v = ((cutout.Y)/cast(float)texture.Height)+pxy;
375 		float v2 = ((cutout.Y+cutout.Height)/cast(float)texture.Height)-pxy;
376 		if ((flip&SpriteFlip.FlipHorizontal)>0) {
377 			float vx = v;
378 			v = v2;
379 			v2 = vx;
380 		}
381 
382 		add_vertex(0, x1, y1, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v); // TOP LEFT
383 		add_vertex(1, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
384 		add_vertex(2, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
385 		add_vertex(3, x2, y2, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v), // TOP RIGHT
386 		add_vertex(4, x3, y3, color.Rf(), color.Gf(), color.Bf(), color.Af(), u2, v2); // BOTTOM RIGHT
387 		add_vertex(5, x4, y4, color.Rf(), color.Gf(), color.Bf(), color.Af(), u, v2); // BOTTOM LEFT
388 		queued++;
389 	}
390 
391 	/**
392 		End ends the sprite batching, allowing you to start a new batch.
393 	*/
394 	public override void End() {
395 		if (!has_begun) throw new Exception("SpriteBatch.Begin must be called before SpriteBatch.End.");
396 		has_begun = false;
397 		Flush();
398 	}
399 }