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 }