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 }