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 }