From acea7b2e728787a0d83bbf83c8c1f042d2c32e7e Mon Sep 17 00:00:00 2001 From: chai <215380520@qq.com> Date: Mon, 3 Jun 2024 10:15:45 +0800 Subject: + plugins project --- .../source/MonoGame.Extended.Graphics/Batcher.cs | 387 +++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 Plugins/MonoGame.Extended/source/MonoGame.Extended.Graphics/Batcher.cs (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Graphics/Batcher.cs') diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Graphics/Batcher.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Graphics/Batcher.cs new file mode 100644 index 0000000..3cb5704 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Graphics/Batcher.cs @@ -0,0 +1,387 @@ +using System; +using System.Runtime.CompilerServices; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Graphics +{ + /// + /// Minimizes draw calls to a by sorting them and attempting to merge them together + /// before submitting them. + /// + /// The type of the information for a draw call. + /// + public abstract class Batcher : IDisposable + where TDrawCallInfo : struct, IBatchDrawCallInfo, IComparable + { + internal const int DefaultBatchMaximumDrawCallsCount = 2048; + private BlendState _blendState; + private SamplerState _samplerState; + private DepthStencilState _depthStencilState; + private RasterizerState _rasterizerState; + private readonly Effect _defaultEffect; + private Effect _currentEffect; + private Matrix? _viewMatrix; + private Matrix? _projectionMatrix; + + /// + /// The array of structs currently enqueued. + /// + protected TDrawCallInfo[] DrawCalls; + + /// + /// The number of structs currently enqueued. + /// + protected int EnqueuedDrawCallCount; + + /// + /// Gets the associated with this . + /// + /// + /// The associated with this . + /// + public GraphicsDevice GraphicsDevice { get; } + + /// + /// Gets a value indicating whether batching is currently in progress by being within a and + /// pair block of code. + /// + /// + /// true if batching has begun; otherwise, false. + /// + public bool HasBegun { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The graphics device. + /// The default effect. + /// + /// The maximum number of structs that can be enqueued before a + /// + /// is required. The default value is 2048. + /// + /// + /// is + /// null. + /// + /// + /// is less than or equal + /// 0. + /// + protected Batcher(GraphicsDevice graphicsDevice, Effect defaultEffect, + int maximumDrawCallsCount = DefaultBatchMaximumDrawCallsCount) + { + if (graphicsDevice == null) + throw new ArgumentNullException(nameof(graphicsDevice)); + + if (maximumDrawCallsCount <= 0) + throw new ArgumentOutOfRangeException(nameof(maximumDrawCallsCount)); + + GraphicsDevice = graphicsDevice; + _defaultEffect = defaultEffect; + DrawCalls = new TDrawCallInfo[maximumDrawCallsCount]; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// + /// true to release both managed and unmanaged resources; false to release only + /// unmanaged resources. + /// + protected virtual void Dispose(bool diposing) + { + if (!diposing) + return; + _defaultEffect.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void EnsureHasBegun([CallerMemberName] string callerMemberName = null) + { + if (!HasBegun) + throw new InvalidOperationException( + $"The {nameof(Begin)} method must be called before the {callerMemberName} method can be called."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void EnsureHasNotBegun([CallerMemberName] string callerMemberName = null) + { + if (HasBegun) + throw new InvalidOperationException( + $"The {nameof(End)} method must be called before the {callerMemberName} method can be called."); + } + + /// + /// Begins the batch operation using an optional , , + /// , , , world-to-view + /// , or view-to-projection . + /// + /// + /// + /// The default objects for , , + /// , and are + /// , , + /// and respectively. + /// Passing + /// null for any of the previously mentioned parameters result in using their default object. + /// + /// + /// The to use for the , pair. + /// + /// The texture to use for the and + /// pair. + /// + /// + /// The to use for the and + /// pair. + /// + /// + /// The to use for the and + /// pair. + /// + /// The to use for the and pair. + /// + /// The world-to-view transformation matrix to use for the and + /// pair. + /// + /// + /// The view-to-projection transformation matrix to use for the and + /// pair. + /// + /// + /// cannot be invoked again until has been invoked. + /// + /// + /// + /// This method must be called before any enqueuing of draw calls. When all the geometry have been enqueued for + /// drawing, call . + /// + /// + public virtual void Begin(Matrix? viewMatrix = null, Matrix? projectionMatrix = null, BlendState blendState = null, SamplerState samplerState = null, + DepthStencilState depthStencilState = null, RasterizerState rasterizerState = null, Effect effect = null) + { + var viewMatrix1 = viewMatrix ?? Matrix.Identity; + var projectionMatrix1 = projectionMatrix ?? Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, -1); + Begin(ref viewMatrix1, ref projectionMatrix1, blendState, samplerState, depthStencilState, rasterizerState, effect); + } + + /// + /// Begins the batch operation using an optional , , + /// , , , world-to-view + /// , or view-to-projection . + /// + /// + /// + /// The default objects for , , + /// , and are + /// , , + /// and respectively. + /// Passing + /// null for any of the previously mentioned parameters result in using their default object. + /// + /// + /// The to use for the , pair. + /// + /// The texture to use for the and + /// pair. + /// + /// + /// The to use for the and + /// pair. + /// + /// + /// The to use for the and + /// pair. + /// + /// The to use for the and pair. + /// + /// The world-to-view transformation matrix to use for the and + /// pair. + /// + /// + /// The view-to-projection transformation matrix to use for the and + /// pair. + /// + /// + /// cannot be invoked again until has been invoked. + /// + /// + /// + /// This method must be called before any enqueuing of draw calls. When all the geometry have been enqueued for + /// drawing, call . + /// + /// + public virtual void Begin(ref Matrix viewMatrix, ref Matrix projectionMatrix, BlendState blendState = null, SamplerState samplerState = null, + DepthStencilState depthStencilState = null, RasterizerState rasterizerState = null, Effect effect = null) + { + EnsureHasNotBegun(); + HasBegun = true; + + // Store the states to be applied on End() + // This ensures that two or more batchers will not affect each other + _blendState = blendState ?? BlendState.AlphaBlend; + _samplerState = samplerState ?? SamplerState.PointClamp; + _depthStencilState = depthStencilState ?? DepthStencilState.None; + _rasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise; + _currentEffect = effect ?? _defaultEffect; + _projectionMatrix = projectionMatrix; + _viewMatrix = viewMatrix; + } + + /// + /// Flushes the batched geometry to the and restores it's state to how it was before + /// was called. + /// + /// + /// cannot be invoked until has been invoked. + /// + /// + /// + /// This method must be called after all enqueuing of draw calls. + /// + /// + public void End() + { + EnsureHasBegun(); + Flush(); + HasBegun = false; + } + + /// + /// Sorts then submits the (sorted) enqueued draw calls to the for + /// rendering without ending the and pair. + /// + protected void Flush() + { + if (EnqueuedDrawCallCount == 0) + return; + SortDrawCallsAndBindBuffers(); + ApplyStates(); + SubmitDrawCalls(); + RestoreStates(); + } + + /// + /// Sorts the enqueued draw calls and binds any used or to the . + /// + protected abstract void SortDrawCallsAndBindBuffers(); + + private void ApplyStates() + { + var oldBlendState = GraphicsDevice.BlendState; + var oldSamplerState = GraphicsDevice.SamplerStates[0]; + var oldDepthStencilState = GraphicsDevice.DepthStencilState; + var oldRasterizerState = GraphicsDevice.RasterizerState; + + GraphicsDevice.BlendState = _blendState; + + GraphicsDevice.SamplerStates[0] = _samplerState; + GraphicsDevice.DepthStencilState = _depthStencilState; + GraphicsDevice.RasterizerState = _rasterizerState; + + _blendState = oldBlendState; + _samplerState = oldSamplerState; + _depthStencilState = oldDepthStencilState; + _rasterizerState = oldRasterizerState; + + var viewMatrix = _viewMatrix ?? Matrix.Identity; + var projectionMatrix = _projectionMatrix ?? + Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, + GraphicsDevice.Viewport.Height, 0, 0f, -1f); + + var matrixChainEffect = _currentEffect as IMatrixChainEffect; + if (matrixChainEffect != null) + { + matrixChainEffect.World = Matrix.Identity; + matrixChainEffect.SetView(ref viewMatrix); + matrixChainEffect.SetProjection(ref projectionMatrix); + } + else + { + var effectMatrices = _currentEffect as IEffectMatrices; + if (effectMatrices == null) + return; + effectMatrices.World = Matrix.Identity; + effectMatrices.View = viewMatrix; + effectMatrices.Projection = projectionMatrix; + } + } + + private void RestoreStates() + { + GraphicsDevice.BlendState = _blendState; + GraphicsDevice.SamplerStates[0] = _samplerState; + GraphicsDevice.DepthStencilState = _depthStencilState; + GraphicsDevice.RasterizerState = _rasterizerState; + } + + /// + /// Enqueues draw call information. + /// + /// The draw call information. + /// + /// + /// If possible, the is merged with the last enqueued draw call information instead of + /// being + /// enqueued. + /// + /// + /// If the enqueue buffer is full, a is invoked and then afterwards + /// is enqueued. + /// + /// + protected void Enqueue(ref TDrawCallInfo drawCall) + { + if (EnqueuedDrawCallCount > 0 && drawCall.TryMerge(ref DrawCalls[EnqueuedDrawCallCount - 1])) + return; + if (EnqueuedDrawCallCount >= DrawCalls.Length) + Flush(); + DrawCalls[EnqueuedDrawCallCount++] = drawCall; + } + + /* It might be better to have derived classes just implement the for loop instead of having this virtual method call... + * However, if the derived class is only going to override this method once and the code is short, which should both be + * true, then maybe we can get away with this virtual method call by having it inlined. So tell the JIT or AOT compiler + * we would like it be so. This does NOT guarantee the compiler will respect our wishes. + */ + + /// + /// Submits a draw operation to the using the specified . + /// + /// The draw call information. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected abstract void InvokeDrawCall(ref TDrawCallInfo drawCall); + + private void SubmitDrawCalls() + { + if (EnqueuedDrawCallCount == 0) + return; + + for (var i = 0; i < EnqueuedDrawCallCount; i++) + { + DrawCalls[i].SetState(_currentEffect); + + foreach (var pass in _currentEffect.CurrentTechnique.Passes) + { + pass.Apply(); + InvokeDrawCall(ref DrawCalls[i]); + } + } + + Array.Clear(DrawCalls, 0, EnqueuedDrawCallCount); + EnqueuedDrawCallCount = 0; + } + } +} + \ No newline at end of file -- cgit v1.1-26-g67d0