A game framework written with osu! in mind.
at master 1015 lines 37 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Concurrent; 6using System.Collections.Generic; 7using System.Diagnostics; 8using osu.Framework.Development; 9using osu.Framework.Graphics.Batches; 10using osu.Framework.Graphics.OpenGL.Textures; 11using osu.Framework.Graphics.Shaders; 12using osu.Framework.Threading; 13using osuTK; 14using osuTK.Graphics; 15using osuTK.Graphics.ES30; 16using osu.Framework.Statistics; 17using osu.Framework.Graphics.Primitives; 18using osu.Framework.Graphics.Colour; 19using osu.Framework.Graphics.OpenGL.Buffers; 20using osu.Framework.Platform; 21using osu.Framework.Timing; 22using static osu.Framework.Threading.ScheduledDelegate; 23 24namespace osu.Framework.Graphics.OpenGL 25{ 26 public static class GLWrapper 27 { 28 /// <summary> 29 /// Maximum number of <see cref="DrawNode"/>s a <see cref="Drawable"/> can draw with. 30 /// This is a carefully-chosen number to enable the update and draw threads to work concurrently without causing unnecessary load. 31 /// </summary> 32 public const int MAX_DRAW_NODES = 3; 33 34 /// <summary> 35 /// The interval (in frames) before checking whether VBOs should be freed. 36 /// VBOs may remain unused for at most double this length before they are recycled. 37 /// </summary> 38 private const int vbo_free_check_interval = 300; 39 40 /// <summary> 41 /// The amount of times <see cref="Reset"/> has been invoked. 42 /// </summary> 43 internal static ulong ResetId { get; private set; } 44 45 public static ref readonly MaskingInfo CurrentMaskingInfo => ref currentMaskingInfo; 46 private static MaskingInfo currentMaskingInfo; 47 48 public static RectangleI Viewport { get; private set; } 49 public static RectangleF Ortho { get; private set; } 50 public static RectangleI Scissor { get; private set; } 51 public static Vector2I ScissorOffset { get; private set; } 52 public static Matrix4 ProjectionMatrix { get; set; } 53 public static DepthInfo CurrentDepthInfo { get; private set; } 54 55 public static float BackbufferDrawDepth { get; private set; } 56 57 public static bool UsingBackbuffer => frame_buffer_stack.Peek() == DefaultFrameBuffer; 58 59 public static int DefaultFrameBuffer; 60 61 public static bool IsEmbedded { get; internal set; } 62 63 /// <summary> 64 /// Check whether we have an initialised and non-disposed GL context. 65 /// </summary> 66 public static bool HasContext => GraphicsContext.CurrentContext != null; 67 68 public static int MaxTextureSize { get; private set; } = 4096; // default value is to allow roughly normal flow in cases we don't have a GL context, like headless CI. 69 public static int MaxRenderBufferSize { get; private set; } = 4096; // default value is to allow roughly normal flow in cases we don't have a GL context, like headless CI. 70 71 /// <summary> 72 /// The maximum number of texture uploads to dequeue and upload per frame. 73 /// Defaults to 32. 74 /// </summary> 75 public static int MaxTexturesUploadedPerFrame { get; set; } = 32; 76 77 /// <summary> 78 /// The maximum number of pixels to upload per frame. 79 /// Defaults to 2 megapixels (8mb alloc). 80 /// </summary> 81 public static int MaxPixelsUploadedPerFrame { get; set; } = 1024 * 1024 * 2; 82 83 private static readonly Scheduler reset_scheduler = new Scheduler(() => ThreadSafety.IsDrawThread, new StopwatchClock(true)); // force no thread set until we are actually on the draw thread. 84 85 /// <summary> 86 /// A queue from which a maximum of one operation is invoked per draw frame. 87 /// </summary> 88 private static readonly ConcurrentQueue<ScheduledDelegate> expensive_operation_queue = new ConcurrentQueue<ScheduledDelegate>(); 89 90 private static readonly ConcurrentQueue<TextureGL> texture_upload_queue = new ConcurrentQueue<TextureGL>(); 91 92 private static readonly List<IVertexBatch> batch_reset_list = new List<IVertexBatch>(); 93 94 private static readonly List<IVertexBuffer> vertex_buffers_in_use = new List<IVertexBuffer>(); 95 96 public static bool IsInitialized { get; private set; } 97 98 private static WeakReference<GameHost> host; 99 100 internal static void Initialize(GameHost host) 101 { 102 if (IsInitialized) return; 103 104 if (host.Window is OsuTKWindow win) 105 IsEmbedded = win.IsEmbedded; 106 107 GLWrapper.host = new WeakReference<GameHost>(host); 108 109 MaxTextureSize = GL.GetInteger(GetPName.MaxTextureSize); 110 MaxRenderBufferSize = GL.GetInteger(GetPName.MaxRenderbufferSize); 111 112 GL.Disable(EnableCap.StencilTest); 113 GL.Enable(EnableCap.Blend); 114 115 IsInitialized = true; 116 117 reset_scheduler.AddDelayed(checkPendingDisposals, 0, true); 118 } 119 120 private static readonly GLDisposalQueue disposal_queue = new GLDisposalQueue(); 121 122 internal static void ScheduleDisposal(Action disposalAction) 123 { 124 if (host != null && host.TryGetTarget(out _)) 125 disposal_queue.ScheduleDisposal(disposalAction); 126 else 127 disposalAction.Invoke(); 128 } 129 130 private static void checkPendingDisposals() 131 { 132 disposal_queue.CheckPendingDisposals(); 133 } 134 135 private static readonly GlobalStatistic<int> stat_expensive_operations_queued = GlobalStatistics.Get<int>(nameof(GLWrapper), "Expensive operation queue length"); 136 private static readonly GlobalStatistic<int> stat_texture_uploads_queued = GlobalStatistics.Get<int>(nameof(GLWrapper), "Texture upload queue length"); 137 private static readonly GlobalStatistic<int> stat_texture_uploads_dequeued = GlobalStatistics.Get<int>(nameof(GLWrapper), "Texture uploads dequeued"); 138 private static readonly GlobalStatistic<int> stat_texture_uploads_performed = GlobalStatistics.Get<int>(nameof(GLWrapper), "Texture uploads performed"); 139 140 internal static void Reset(Vector2 size) 141 { 142 ResetId++; 143 144 Trace.Assert(shader_stack.Count == 0); 145 146 reset_scheduler.Update(); 147 148 stat_expensive_operations_queued.Value = expensive_operation_queue.Count; 149 150 while (expensive_operation_queue.TryDequeue(out ScheduledDelegate operation)) 151 { 152 if (operation.State == RunState.Waiting) 153 { 154 operation.RunTask(); 155 break; 156 } 157 } 158 159 lastActiveBatch = null; 160 lastBlendingParameters = new BlendingParameters(); 161 lastBlendingEnabledState = null; 162 163 foreach (var b in batch_reset_list) 164 b.ResetCounters(); 165 batch_reset_list.Clear(); 166 167 viewport_stack.Clear(); 168 ortho_stack.Clear(); 169 masking_stack.Clear(); 170 scissor_rect_stack.Clear(); 171 frame_buffer_stack.Clear(); 172 depth_stack.Clear(); 173 scissor_state_stack.Clear(); 174 scissor_offset_stack.Clear(); 175 176 BindFrameBuffer(DefaultFrameBuffer); 177 178 Scissor = RectangleI.Empty; 179 ScissorOffset = Vector2I.Zero; 180 Viewport = RectangleI.Empty; 181 Ortho = RectangleF.Empty; 182 183 PushScissorState(true); 184 PushViewport(new RectangleI(0, 0, (int)size.X, (int)size.Y)); 185 PushScissor(new RectangleI(0, 0, (int)size.X, (int)size.Y)); 186 PushScissorOffset(Vector2I.Zero); 187 PushMaskingInfo(new MaskingInfo 188 { 189 ScreenSpaceAABB = new RectangleI(0, 0, (int)size.X, (int)size.Y), 190 MaskingRect = new RectangleF(0, 0, size.X, size.Y), 191 ToMaskingSpace = Matrix3.Identity, 192 BlendRange = 1, 193 AlphaExponent = 1, 194 CornerExponent = 2.5f, 195 }, true); 196 197 PushDepthInfo(DepthInfo.Default); 198 Clear(new ClearInfo(Color4.Black)); 199 200 freeUnusedVertexBuffers(); 201 202 stat_texture_uploads_queued.Value = texture_upload_queue.Count; 203 stat_texture_uploads_dequeued.Value = 0; 204 stat_texture_uploads_performed.Value = 0; 205 206 // increase the number of items processed with the queue length to ensure it doesn't get out of hand. 207 int targetUploads = Math.Clamp(texture_upload_queue.Count / 2, 1, MaxTexturesUploadedPerFrame); 208 int uploads = 0; 209 int uploadedPixels = 0; 210 211 // continue attempting to upload textures until enough uploads have been performed. 212 while (texture_upload_queue.TryDequeue(out TextureGL texture)) 213 { 214 stat_texture_uploads_dequeued.Value++; 215 216 texture.IsQueuedForUpload = false; 217 218 if (!texture.Upload()) 219 continue; 220 221 stat_texture_uploads_performed.Value++; 222 223 if (++uploads >= targetUploads) 224 break; 225 226 if ((uploadedPixels += texture.Width * texture.Height) > MaxPixelsUploadedPerFrame) 227 break; 228 } 229 230 last_bound_texture.AsSpan().Clear(); 231 last_bound_texture_is_atlas.AsSpan().Clear(); 232 last_bound_buffers.AsSpan().Clear(); 233 } 234 235 private static ClearInfo currentClearInfo; 236 237 public static void Clear(ClearInfo clearInfo) 238 { 239 PushDepthInfo(new DepthInfo(writeDepth: true)); 240 PushScissorState(false); 241 if (clearInfo.Colour != currentClearInfo.Colour) 242 GL.ClearColor(clearInfo.Colour); 243 244 if (clearInfo.Depth != currentClearInfo.Depth) 245 { 246 if (IsEmbedded) 247 { 248 // GL ES only supports glClearDepthf 249 // See: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glClearDepthf.xhtml 250 GL.ClearDepth((float)clearInfo.Depth); 251 } 252 else 253 { 254 // Older desktop platforms don't support glClearDepthf, so standard GL's double version is used instead 255 // See: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glClearDepth.xhtml 256 osuTK.Graphics.OpenGL.GL.ClearDepth(clearInfo.Depth); 257 } 258 } 259 260 if (clearInfo.Stencil != currentClearInfo.Stencil) 261 GL.ClearStencil(clearInfo.Stencil); 262 263 GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); 264 265 currentClearInfo = clearInfo; 266 267 PopScissorState(); 268 PopDepthInfo(); 269 } 270 271 private static readonly Stack<bool> scissor_state_stack = new Stack<bool>(); 272 273 private static bool currentScissorState; 274 275 public static void PushScissorState(bool enabled) 276 { 277 scissor_state_stack.Push(enabled); 278 setScissorState(enabled); 279 } 280 281 public static void PopScissorState() 282 { 283 Trace.Assert(scissor_state_stack.Count > 1); 284 285 scissor_state_stack.Pop(); 286 287 setScissorState(scissor_state_stack.Peek()); 288 } 289 290 private static void setScissorState(bool enabled) 291 { 292 if (enabled == currentScissorState) 293 return; 294 295 currentScissorState = enabled; 296 297 if (enabled) 298 GL.Enable(EnableCap.ScissorTest); 299 else 300 GL.Disable(EnableCap.ScissorTest); 301 } 302 303 /// <summary> 304 /// Enqueues a texture to be uploaded in the next frame. 305 /// </summary> 306 /// <param name="texture">The texture to be uploaded.</param> 307 public static void EnqueueTextureUpload(TextureGL texture) 308 { 309 if (texture.IsQueuedForUpload) 310 return; 311 312 if (host != null) 313 { 314 texture.IsQueuedForUpload = true; 315 texture_upload_queue.Enqueue(texture); 316 } 317 } 318 319 /// <summary> 320 /// Schedules an expensive operation to a queue from which a maximum of one operation is performed per frame. 321 /// </summary> 322 /// <param name="operation">The operation to schedule.</param> 323 public static void ScheduleExpensiveOperation(ScheduledDelegate operation) 324 { 325 if (host != null) 326 expensive_operation_queue.Enqueue(operation); 327 } 328 329 private static readonly int[] last_bound_buffers = new int[2]; 330 331 /// <summary> 332 /// Bind an OpenGL buffer object. 333 /// </summary> 334 /// <param name="target">The buffer type to bind.</param> 335 /// <param name="buffer">The buffer ID to bind.</param> 336 /// <returns>Whether an actual bind call was necessary. This value is false when repeatedly binding the same buffer.</returns> 337 public static bool BindBuffer(BufferTarget target, int buffer) 338 { 339 int bufferIndex = target - BufferTarget.ArrayBuffer; 340 if (last_bound_buffers[bufferIndex] == buffer) 341 return false; 342 343 last_bound_buffers[bufferIndex] = buffer; 344 GL.BindBuffer(target, buffer); 345 346 FrameStatistics.Increment(StatisticsCounterType.VBufBinds); 347 348 return true; 349 } 350 351 private static IVertexBatch lastActiveBatch; 352 353 /// <summary> 354 /// Sets the last vertex batch used for drawing. 355 /// <para> 356 /// This is done so that various methods that change GL state can force-draw the batch 357 /// before continuing with the state change. 358 /// </para> 359 /// </summary> 360 /// <param name="batch">The batch.</param> 361 internal static void SetActiveBatch(IVertexBatch batch) 362 { 363 if (lastActiveBatch == batch) 364 return; 365 366 batch_reset_list.Add(batch); 367 368 FlushCurrentBatch(); 369 370 lastActiveBatch = batch; 371 } 372 373 /// <summary> 374 /// Notifies that a <see cref="IVertexBuffer"/> has begun being used. 375 /// </summary> 376 /// <param name="buffer">The <see cref="IVertexBuffer"/> in use.</param> 377 internal static void RegisterVertexBufferUse(IVertexBuffer buffer) => vertex_buffers_in_use.Add(buffer); 378 379 private static void freeUnusedVertexBuffers() 380 { 381 if (ResetId % vbo_free_check_interval != 0) 382 return; 383 384 foreach (var buf in vertex_buffers_in_use) 385 { 386 if (buf.InUse && ResetId - buf.LastUseResetId > vbo_free_check_interval) 387 buf.Free(); 388 } 389 390 vertex_buffers_in_use.RemoveAll(b => !b.InUse); 391 } 392 393 private static readonly int[] last_bound_texture = new int[16]; 394 private static readonly bool[] last_bound_texture_is_atlas = new bool[16]; 395 396 internal static int GetTextureUnitId(TextureUnit unit) => (int)unit - (int)TextureUnit.Texture0; 397 internal static bool AtlasTextureIsBound(TextureUnit unit) => last_bound_texture_is_atlas[GetTextureUnitId(unit)]; 398 399 /// <summary> 400 /// Binds a texture to draw with. 401 /// </summary> 402 /// <param name="texture">The texture to bind.</param> 403 /// <param name="unit">The texture unit to bind it to.</param> 404 /// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param> 405 /// <param name="wrapModeT">The texture wrap mode in vertical direction.</param> 406 /// <returns>true if the provided texture was not already bound (causing a binding change).</returns> 407 public static bool BindTexture(TextureGL texture, TextureUnit unit = TextureUnit.Texture0, WrapMode wrapModeS = WrapMode.None, WrapMode wrapModeT = WrapMode.None) 408 { 409 bool didBind = BindTexture(texture?.TextureId ?? 0, unit, wrapModeS, wrapModeT); 410 last_bound_texture_is_atlas[GetTextureUnitId(unit)] = texture is TextureGLAtlas; 411 412 return didBind; 413 } 414 415 internal static WrapMode CurrentWrapModeS; 416 internal static WrapMode CurrentWrapModeT; 417 418 /// <summary> 419 /// Binds a texture to draw with. 420 /// </summary> 421 /// <param name="textureId">The texture to bind.</param> 422 /// <param name="unit">The texture unit to bind it to.</param> 423 /// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param> 424 /// <param name="wrapModeT">The texture wrap mode in vertical direction.</param> 425 /// <returns>true if the provided texture was not already bound (causing a binding change).</returns> 426 public static bool BindTexture(int textureId, TextureUnit unit = TextureUnit.Texture0, WrapMode wrapModeS = WrapMode.None, WrapMode wrapModeT = WrapMode.None) 427 { 428 var index = GetTextureUnitId(unit); 429 430 if (wrapModeS != CurrentWrapModeS) 431 { 432 GlobalPropertyManager.Set(GlobalProperty.WrapModeS, (int)wrapModeS); 433 CurrentWrapModeS = wrapModeS; 434 } 435 436 if (wrapModeT != CurrentWrapModeT) 437 { 438 GlobalPropertyManager.Set(GlobalProperty.WrapModeT, (int)wrapModeT); 439 CurrentWrapModeT = wrapModeT; 440 } 441 442 if (last_bound_texture[index] == textureId) 443 return false; 444 445 FlushCurrentBatch(); 446 447 GL.ActiveTexture(unit); 448 GL.BindTexture(TextureTarget.Texture2D, textureId); 449 450 last_bound_texture[index] = textureId; 451 last_bound_texture_is_atlas[GetTextureUnitId(unit)] = false; 452 453 FrameStatistics.Increment(StatisticsCounterType.TextureBinds); 454 return true; 455 } 456 457 private static BlendingParameters lastBlendingParameters; 458 private static bool? lastBlendingEnabledState; 459 460 /// <summary> 461 /// Sets the blending function to draw with. 462 /// </summary> 463 /// <param name="blendingParameters">The info we should use to update the active state.</param> 464 public static void SetBlend(BlendingParameters blendingParameters) 465 { 466 if (lastBlendingParameters == blendingParameters) 467 return; 468 469 FlushCurrentBatch(); 470 471 if (blendingParameters.IsDisabled) 472 { 473 if (!lastBlendingEnabledState.HasValue || lastBlendingEnabledState.Value) 474 GL.Disable(EnableCap.Blend); 475 476 lastBlendingEnabledState = false; 477 } 478 else 479 { 480 if (!lastBlendingEnabledState.HasValue || !lastBlendingEnabledState.Value) 481 GL.Enable(EnableCap.Blend); 482 483 lastBlendingEnabledState = true; 484 485 GL.BlendEquationSeparate(blendingParameters.RGBEquationMode, blendingParameters.AlphaEquationMode); 486 GL.BlendFuncSeparate(blendingParameters.SourceBlendingFactor, blendingParameters.DestinationBlendingFactor, 487 blendingParameters.SourceAlphaBlendingFactor, blendingParameters.DestinationAlphaBlendingFactor); 488 } 489 490 lastBlendingParameters = blendingParameters; 491 } 492 493 private static readonly Stack<RectangleI> viewport_stack = new Stack<RectangleI>(); 494 495 /// <summary> 496 /// Applies a new viewport rectangle. 497 /// </summary> 498 /// <param name="viewport">The viewport rectangle.</param> 499 public static void PushViewport(RectangleI viewport) 500 { 501 var actualRect = viewport; 502 503 if (actualRect.Width < 0) 504 { 505 actualRect.X += viewport.Width; 506 actualRect.Width = -viewport.Width; 507 } 508 509 if (actualRect.Height < 0) 510 { 511 actualRect.Y += viewport.Height; 512 actualRect.Height = -viewport.Height; 513 } 514 515 PushOrtho(viewport); 516 517 viewport_stack.Push(actualRect); 518 519 if (Viewport == actualRect) 520 return; 521 522 Viewport = actualRect; 523 524 GL.Viewport(Viewport.Left, Viewport.Top, Viewport.Width, Viewport.Height); 525 } 526 527 /// <summary> 528 /// Applies the last viewport rectangle. 529 /// </summary> 530 public static void PopViewport() 531 { 532 Trace.Assert(viewport_stack.Count > 1); 533 534 PopOrtho(); 535 536 viewport_stack.Pop(); 537 RectangleI actualRect = viewport_stack.Peek(); 538 539 if (Viewport == actualRect) 540 return; 541 542 Viewport = actualRect; 543 544 GL.Viewport(Viewport.Left, Viewport.Top, Viewport.Width, Viewport.Height); 545 } 546 547 /// <summary> 548 /// Applies a new scissor rectangle. 549 /// </summary> 550 /// <param name="scissor">The scissor rectangle.</param> 551 public static void PushScissor(RectangleI scissor) 552 { 553 FlushCurrentBatch(); 554 555 scissor_rect_stack.Push(scissor); 556 if (Scissor == scissor) 557 return; 558 559 Scissor = scissor; 560 setScissor(scissor); 561 } 562 563 /// <summary> 564 /// Applies the last scissor rectangle. 565 /// </summary> 566 public static void PopScissor() 567 { 568 Trace.Assert(scissor_rect_stack.Count > 1); 569 570 FlushCurrentBatch(); 571 572 scissor_rect_stack.Pop(); 573 RectangleI scissor = scissor_rect_stack.Peek(); 574 575 if (Scissor == scissor) 576 return; 577 578 Scissor = scissor; 579 setScissor(scissor); 580 } 581 582 private static void setScissor(RectangleI scissor) 583 { 584 if (scissor.Width < 0) 585 { 586 scissor.X += scissor.Width; 587 scissor.Width = -scissor.Width; 588 } 589 590 if (scissor.Height < 0) 591 { 592 scissor.Y += scissor.Height; 593 scissor.Height = -scissor.Height; 594 } 595 596 GL.Scissor(scissor.X, Viewport.Height - scissor.Bottom, scissor.Width, scissor.Height); 597 } 598 599 private static readonly Stack<Vector2I> scissor_offset_stack = new Stack<Vector2I>(); 600 601 /// <summary> 602 /// Applies an offset to the scissor rectangle. 603 /// </summary> 604 /// <param name="offset">The offset.</param> 605 public static void PushScissorOffset(Vector2I offset) 606 { 607 FlushCurrentBatch(); 608 609 scissor_offset_stack.Push(offset); 610 if (ScissorOffset == offset) 611 return; 612 613 ScissorOffset = offset; 614 } 615 616 /// <summary> 617 /// Applies the last scissor rectangle offset. 618 /// </summary> 619 public static void PopScissorOffset() 620 { 621 Trace.Assert(scissor_offset_stack.Count > 1); 622 623 FlushCurrentBatch(); 624 625 scissor_offset_stack.Pop(); 626 Vector2I offset = scissor_offset_stack.Peek(); 627 628 if (ScissorOffset == offset) 629 return; 630 631 ScissorOffset = offset; 632 } 633 634 private static readonly Stack<RectangleF> ortho_stack = new Stack<RectangleF>(); 635 636 /// <summary> 637 /// Applies a new orthographic projection rectangle. 638 /// </summary> 639 /// <param name="ortho">The orthographic projection rectangle.</param> 640 public static void PushOrtho(RectangleF ortho) 641 { 642 FlushCurrentBatch(); 643 644 ortho_stack.Push(ortho); 645 if (Ortho == ortho) 646 return; 647 648 Ortho = ortho; 649 650 ProjectionMatrix = Matrix4.CreateOrthographicOffCenter(Ortho.Left, Ortho.Right, Ortho.Bottom, Ortho.Top, -1, 1); 651 GlobalPropertyManager.Set(GlobalProperty.ProjMatrix, ProjectionMatrix); 652 } 653 654 /// <summary> 655 /// Applies the last orthographic projection rectangle. 656 /// </summary> 657 public static void PopOrtho() 658 { 659 Trace.Assert(ortho_stack.Count > 1); 660 661 FlushCurrentBatch(); 662 663 ortho_stack.Pop(); 664 RectangleF actualRect = ortho_stack.Peek(); 665 666 if (Ortho == actualRect) 667 return; 668 669 Ortho = actualRect; 670 671 ProjectionMatrix = Matrix4.CreateOrthographicOffCenter(Ortho.Left, Ortho.Right, Ortho.Bottom, Ortho.Top, -1, 1); 672 GlobalPropertyManager.Set(GlobalProperty.ProjMatrix, ProjectionMatrix); 673 } 674 675 private static readonly Stack<MaskingInfo> masking_stack = new Stack<MaskingInfo>(); 676 private static readonly Stack<RectangleI> scissor_rect_stack = new Stack<RectangleI>(); 677 private static readonly Stack<int> frame_buffer_stack = new Stack<int>(); 678 private static readonly Stack<DepthInfo> depth_stack = new Stack<DepthInfo>(); 679 680 private static void setMaskingInfo(MaskingInfo maskingInfo, bool isPushing, bool overwritePreviousScissor) 681 { 682 FlushCurrentBatch(); 683 684 GlobalPropertyManager.Set(GlobalProperty.MaskingRect, new Vector4( 685 maskingInfo.MaskingRect.Left, 686 maskingInfo.MaskingRect.Top, 687 maskingInfo.MaskingRect.Right, 688 maskingInfo.MaskingRect.Bottom)); 689 690 GlobalPropertyManager.Set(GlobalProperty.ToMaskingSpace, maskingInfo.ToMaskingSpace); 691 692 GlobalPropertyManager.Set(GlobalProperty.CornerRadius, maskingInfo.CornerRadius); 693 GlobalPropertyManager.Set(GlobalProperty.CornerExponent, maskingInfo.CornerExponent); 694 695 GlobalPropertyManager.Set(GlobalProperty.BorderThickness, maskingInfo.BorderThickness / maskingInfo.BlendRange); 696 697 if (maskingInfo.BorderThickness > 0) 698 { 699 GlobalPropertyManager.Set(GlobalProperty.BorderColour, new Vector4( 700 maskingInfo.BorderColour.Linear.R, 701 maskingInfo.BorderColour.Linear.G, 702 maskingInfo.BorderColour.Linear.B, 703 maskingInfo.BorderColour.Linear.A)); 704 } 705 706 GlobalPropertyManager.Set(GlobalProperty.MaskingBlendRange, maskingInfo.BlendRange); 707 GlobalPropertyManager.Set(GlobalProperty.AlphaExponent, maskingInfo.AlphaExponent); 708 709 GlobalPropertyManager.Set(GlobalProperty.EdgeOffset, maskingInfo.EdgeOffset); 710 711 GlobalPropertyManager.Set(GlobalProperty.DiscardInner, maskingInfo.Hollow); 712 if (maskingInfo.Hollow) 713 GlobalPropertyManager.Set(GlobalProperty.InnerCornerRadius, maskingInfo.HollowCornerRadius); 714 715 if (isPushing) 716 { 717 // When drawing to a viewport that doesn't match the projection size (e.g. via framebuffers), the resultant image will be scaled 718 Vector2 viewportScale = Vector2.Divide(Viewport.Size, Ortho.Size); 719 720 Vector2 location = (maskingInfo.ScreenSpaceAABB.Location - ScissorOffset) * viewportScale; 721 Vector2 size = maskingInfo.ScreenSpaceAABB.Size * viewportScale; 722 723 RectangleI actualRect = new RectangleI( 724 (int)Math.Floor(location.X), 725 (int)Math.Floor(location.Y), 726 (int)Math.Ceiling(size.X), 727 (int)Math.Ceiling(size.Y)); 728 729 PushScissor(overwritePreviousScissor ? actualRect : RectangleI.Intersect(scissor_rect_stack.Peek(), actualRect)); 730 } 731 else 732 PopScissor(); 733 } 734 735 internal static void FlushCurrentBatch() 736 { 737 lastActiveBatch?.Draw(); 738 } 739 740 public static bool IsMaskingActive => masking_stack.Count > 1; 741 742 /// <summary> 743 /// Applies a new scissor rectangle. 744 /// </summary> 745 /// <param name="maskingInfo">The masking info.</param> 746 /// <param name="overwritePreviousScissor">Whether or not to shrink an existing scissor rectangle.</param> 747 public static void PushMaskingInfo(in MaskingInfo maskingInfo, bool overwritePreviousScissor = false) 748 { 749 masking_stack.Push(maskingInfo); 750 if (CurrentMaskingInfo == maskingInfo) 751 return; 752 753 currentMaskingInfo = maskingInfo; 754 setMaskingInfo(CurrentMaskingInfo, true, overwritePreviousScissor); 755 } 756 757 /// <summary> 758 /// Applies the last scissor rectangle. 759 /// </summary> 760 public static void PopMaskingInfo() 761 { 762 Trace.Assert(masking_stack.Count > 1); 763 764 masking_stack.Pop(); 765 MaskingInfo maskingInfo = masking_stack.Peek(); 766 767 if (CurrentMaskingInfo == maskingInfo) 768 return; 769 770 currentMaskingInfo = maskingInfo; 771 setMaskingInfo(CurrentMaskingInfo, false, true); 772 } 773 774 /// <summary> 775 /// Applies a new depth information. 776 /// </summary> 777 /// <param name="depthInfo">The depth information.</param> 778 public static void PushDepthInfo(DepthInfo depthInfo) 779 { 780 depth_stack.Push(depthInfo); 781 782 if (CurrentDepthInfo.Equals(depthInfo)) 783 return; 784 785 CurrentDepthInfo = depthInfo; 786 setDepthInfo(CurrentDepthInfo); 787 } 788 789 /// <summary> 790 /// Applies the last depth information. 791 /// </summary> 792 public static void PopDepthInfo() 793 { 794 Trace.Assert(depth_stack.Count > 1); 795 796 depth_stack.Pop(); 797 DepthInfo depthInfo = depth_stack.Peek(); 798 799 if (CurrentDepthInfo.Equals(depthInfo)) 800 return; 801 802 CurrentDepthInfo = depthInfo; 803 setDepthInfo(CurrentDepthInfo); 804 } 805 806 private static void setDepthInfo(DepthInfo depthInfo) 807 { 808 FlushCurrentBatch(); 809 810 if (depthInfo.DepthTest) 811 { 812 GL.Enable(EnableCap.DepthTest); 813 GL.DepthFunc(depthInfo.Function); 814 } 815 else 816 GL.Disable(EnableCap.DepthTest); 817 818 GL.DepthMask(depthInfo.WriteDepth); 819 } 820 821 /// <summary> 822 /// Sets the current draw depth. 823 /// The draw depth is written to every vertex added to <see cref="VertexBuffer{T}"/>s. 824 /// </summary> 825 /// <param name="drawDepth">The draw depth.</param> 826 internal static void SetDrawDepth(float drawDepth) => BackbufferDrawDepth = drawDepth; 827 828 /// <summary> 829 /// Binds a framebuffer. 830 /// </summary> 831 /// <param name="frameBuffer">The framebuffer to bind.</param> 832 public static void BindFrameBuffer(int frameBuffer) 833 { 834 if (frameBuffer == -1) return; 835 836 bool alreadyBound = frame_buffer_stack.Count > 0 && frame_buffer_stack.Peek() == frameBuffer; 837 838 frame_buffer_stack.Push(frameBuffer); 839 840 if (!alreadyBound) 841 { 842 FlushCurrentBatch(); 843 GL.BindFramebuffer(FramebufferTarget.Framebuffer, frameBuffer); 844 845 GlobalPropertyManager.Set(GlobalProperty.BackbufferDraw, UsingBackbuffer); 846 } 847 848 GlobalPropertyManager.Set(GlobalProperty.GammaCorrection, UsingBackbuffer); 849 } 850 851 /// <summary> 852 /// Binds a framebuffer. 853 /// </summary> 854 /// <param name="frameBuffer">The framebuffer to bind.</param> 855 public static void UnbindFrameBuffer(int frameBuffer) 856 { 857 if (frameBuffer == -1) return; 858 859 if (frame_buffer_stack.Peek() != frameBuffer) 860 return; 861 862 frame_buffer_stack.Pop(); 863 864 FlushCurrentBatch(); 865 GL.BindFramebuffer(FramebufferTarget.Framebuffer, frame_buffer_stack.Peek()); 866 867 GlobalPropertyManager.Set(GlobalProperty.BackbufferDraw, UsingBackbuffer); 868 GlobalPropertyManager.Set(GlobalProperty.GammaCorrection, UsingBackbuffer); 869 } 870 871 /// <summary> 872 /// Deletes a frame buffer. 873 /// </summary> 874 /// <param name="frameBuffer">The frame buffer to delete.</param> 875 internal static void DeleteFrameBuffer(int frameBuffer) 876 { 877 if (frameBuffer == -1) return; 878 879 while (frame_buffer_stack.Peek() == frameBuffer) 880 UnbindFrameBuffer(frameBuffer); 881 882 ScheduleDisposal(() => { GL.DeleteFramebuffer(frameBuffer); }); 883 } 884 885 private static int currentShader; 886 887 private static readonly Stack<int> shader_stack = new Stack<int>(); 888 889 public static void UseProgram(int? shader) 890 { 891 ThreadSafety.EnsureDrawThread(); 892 893 if (shader != null) 894 { 895 shader_stack.Push(shader.Value); 896 } 897 else 898 { 899 shader_stack.Pop(); 900 901 //check if the stack is empty, and if so don't restore the previous shader. 902 if (shader_stack.Count == 0) 903 return; 904 } 905 906 int s = shader ?? shader_stack.Peek(); 907 908 if (currentShader == s) return; 909 910 FrameStatistics.Increment(StatisticsCounterType.ShaderBinds); 911 912 FlushCurrentBatch(); 913 914 GL.UseProgram(s); 915 currentShader = s; 916 } 917 918 internal static void SetUniform<T>(IUniformWithValue<T> uniform) 919 where T : struct, IEquatable<T> 920 { 921 if (uniform.Owner == currentShader) 922 FlushCurrentBatch(); 923 924 switch (uniform) 925 { 926 case IUniformWithValue<bool> b: 927 GL.Uniform1(uniform.Location, b.GetValue() ? 1 : 0); 928 break; 929 930 case IUniformWithValue<int> i: 931 GL.Uniform1(uniform.Location, i.GetValue()); 932 break; 933 934 case IUniformWithValue<float> f: 935 GL.Uniform1(uniform.Location, f.GetValue()); 936 break; 937 938 case IUniformWithValue<Vector2> v2: 939 GL.Uniform2(uniform.Location, ref v2.GetValueByRef()); 940 break; 941 942 case IUniformWithValue<Vector3> v3: 943 GL.Uniform3(uniform.Location, ref v3.GetValueByRef()); 944 break; 945 946 case IUniformWithValue<Vector4> v4: 947 GL.Uniform4(uniform.Location, ref v4.GetValueByRef()); 948 break; 949 950 case IUniformWithValue<Matrix2> m2: 951 GL.UniformMatrix2(uniform.Location, false, ref m2.GetValueByRef()); 952 break; 953 954 case IUniformWithValue<Matrix3> m3: 955 GL.UniformMatrix3(uniform.Location, false, ref m3.GetValueByRef()); 956 break; 957 958 case IUniformWithValue<Matrix4> m4: 959 GL.UniformMatrix4(uniform.Location, false, ref m4.GetValueByRef()); 960 break; 961 } 962 } 963 } 964 965 public struct MaskingInfo : IEquatable<MaskingInfo> 966 { 967 public RectangleI ScreenSpaceAABB; 968 public RectangleF MaskingRect; 969 970 public Quad ConservativeScreenSpaceQuad; 971 972 /// <summary> 973 /// This matrix transforms screen space coordinates to masking space (likely the parent 974 /// space of the container doing the masking). 975 /// It is used by a shader to determine which pixels to discard. 976 /// </summary> 977 public Matrix3 ToMaskingSpace; 978 979 public float CornerRadius; 980 public float CornerExponent; 981 982 public float BorderThickness; 983 public SRGBColour BorderColour; 984 985 public float BlendRange; 986 public float AlphaExponent; 987 988 public Vector2 EdgeOffset; 989 990 public bool Hollow; 991 public float HollowCornerRadius; 992 993 public readonly bool Equals(MaskingInfo other) => this == other; 994 995 public static bool operator ==(in MaskingInfo left, in MaskingInfo right) => 996 left.ScreenSpaceAABB == right.ScreenSpaceAABB && 997 left.MaskingRect == right.MaskingRect && 998 left.ToMaskingSpace == right.ToMaskingSpace && 999 left.CornerRadius == right.CornerRadius && 1000 left.CornerExponent == right.CornerExponent && 1001 left.BorderThickness == right.BorderThickness && 1002 left.BorderColour.Equals(right.BorderColour) && 1003 left.BlendRange == right.BlendRange && 1004 left.AlphaExponent == right.AlphaExponent && 1005 left.EdgeOffset == right.EdgeOffset && 1006 left.Hollow == right.Hollow && 1007 left.HollowCornerRadius == right.HollowCornerRadius; 1008 1009 public static bool operator !=(in MaskingInfo left, in MaskingInfo right) => !(left == right); 1010 1011 public override readonly bool Equals(object obj) => obj is MaskingInfo other && this == other; 1012 1013 public override readonly int GetHashCode() => 0; // Shouldn't be used; simplifying implementation here. 1014 } 1015}