Create Graphics library. (#316)

* Fix BoundingRect unit tests.

* Create `graphics` library.
This commit is contained in:
Lucas Girouard-Stranks
2016-11-27 22:51:59 +10:00
committed by Dylan Wilson
parent d1ffba93f2
commit d0c621d0dc
35 changed files with 139 additions and 71 deletions
@@ -0,0 +1,69 @@
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
/// <summary>
/// Defines options for how two-dimensional geometric objects are submitted to a <see cref="GraphicsDevice" />.
/// </summary>
public enum Batch2DSortMode : byte
{
/// <summary>
/// Each draw call invokes
/// <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> immediately for each
/// <see cref="EffectPass" />.
/// </summary>
Immediate,
/// <summary>
/// Each draw call is added to the end of an array of draw commands. Draw commands will be merged, if possible, for
/// batching. When <see cref="DynamicBatch2D.End" /> is called, the merged draw commands are processed on a first come,
/// first
/// serve, basis where each command invokes
/// <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> for each
/// <see cref="EffectPass" />.
/// </summary>
Deferred,
/// <summary>
/// Each draw call is added to the end of an array of draw commands. Draw commands will be merged, if possible, for
/// batching as they are enqueued and after they are sorted by their <see cref="Texture2D" />s. When
/// <see cref="DynamicBatch2D.End" /> is called, the sorting takes place and then the merged draw commands are
/// processed where
/// each command invokes <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> for each
/// <see cref="EffectPass" />.
/// </summary>
Texture,
/// <summary>
/// Each draw call is added to the end of an array of draw commands. Draw commands will be merged, if possible, for
/// batching as they are enqueued and after they are sorted by their depth values in ascending order. When
/// <see cref="DynamicBatch2D.End" /> is called, the sorting takes place and then the merged draw commands are
/// processed where
/// each command invokes <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> for each
/// <see cref="EffectPass" />.
/// </summary>
/// <remarks>
/// <para>
/// Depth values, by default, range from <code>0.0f</code> to <code>1.0f</code>, inclusively, where smaller values
/// such as <code>0.0f</code> are considered in-front of larger values such as <code>1.0f</code>.
/// </para>
/// </remarks>
FrontToBack,
/// <summary>
/// Each draw call is added to the end of an array of draw commands. Draw commands will be merged, if possible, for
/// batching as they are enqueued and after they are sorted by their depth values in descending order. When
/// <see cref="DynamicBatch2D.End" /> is called, the sorting takes place and then the merged draw commands are
/// processed where
/// each command invokes <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> for each
/// <see cref="EffectPass" />.
/// </summary>
/// <remarks>
/// <para>
/// Depth values, by default, range from <code>0.0f</code> to <code>1.0f</code>, inclusively, where smaller values
/// such as <code>0.0f</code> are considered in-front of larger values such as <code>1.0f</code>.
/// </para>
/// </remarks>
BackToFront
}
}
@@ -0,0 +1,248 @@
using System;
using System.Runtime.CompilerServices;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
// why batching? see top answer: http://answers.unity3d.com/questions/14578/whats-the-best-way-to-reduce-draw-calls.html
/// <summary>
/// Enables a group of geometric objects to be drawn using the same settings.
/// </summary>
/// <typeparam name="TVertexType">The type of vertex.</typeparam>
/// <typeparam name="TBatchDrawCommandData">The type of data stored with each draw command.</typeparam>
/// <seealso cref="IDisposable" />
/// <remarks>
/// <para>
/// For best performance, use <see cref="DynamicGeometryBuffer{TVertexType}" /> when instantiating
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}" /> for geometry which changes frame-to-frame. For geometry
/// which does not change frame-to-frame, or changes infrequently between frames, use
/// <see cref="StaticGeometryBuffer{TVertexType}" /> instead.
/// </para>
/// </remarks>
public abstract class Batch<TVertexType, TBatchDrawCommandData> : IDisposable
where TVertexType : struct, IVertexType
where TBatchDrawCommandData : struct, IBatchDrawCommandData<TBatchDrawCommandData>
{
internal const int DefaultMaximumBatchCommandsCount = 2048;
private BatchCommandDrawer<TVertexType, TBatchDrawCommandData> _commandDrawer;
private BatchCommandQueue<TVertexType, TBatchDrawCommandData> _currentCommandQueue;
private DeferredBatchCommandQueue<TVertexType, TBatchDrawCommandData> _deferredCommandQueue;
private ImmediateBatchCommandQueue<TVertexType, TBatchDrawCommandData> _immediateCommandQueue;
/// <summary>
/// Initializes a new instance of the <see cref="Batch{TVertexType,TBatchDrawCommandData}" /> class.
/// </summary>
/// <param name="geometryBuffer">The geometry buffer.</param>
/// <param name="maximumBatchCommandsCount">
/// The maximum number of batch draw commands that can be deferred. The default value is <code>2048</code>.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="geometryBuffer" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="maximumBatchCommandsCount" /> is less than or equal
/// <code>0</code>.
/// </exception>
/// <remarks>
/// <para>
/// For best performance, use <see cref="DynamicGeometryBuffer{TVertexType}" /> for geometry which changes
/// frame-to-frame and <see cref="StaticGeometryBuffer{TVertexType}" /> for geoemtry which does not change
/// frame-to-frame, or changes infrequently between frames.
/// </para>
/// </remarks>
protected Batch(GeometryBuffer<TVertexType> geometryBuffer,
int maximumBatchCommandsCount = DefaultMaximumBatchCommandsCount)
{
if (geometryBuffer == null)
throw new ArgumentNullException(nameof(geometryBuffer));
if (maximumBatchCommandsCount <= 0)
throw new ArgumentOutOfRangeException(nameof(maximumBatchCommandsCount));
GeometryBuffer = geometryBuffer;
GraphicsDevice = geometryBuffer.GraphicsDevice;
_commandDrawer = new BatchCommandDrawer<TVertexType, TBatchDrawCommandData>(GraphicsDevice, GeometryBuffer);
_immediateCommandQueue = new ImmediateBatchCommandQueue<TVertexType, TBatchDrawCommandData>(GraphicsDevice,
_commandDrawer);
_deferredCommandQueue = new DeferredBatchCommandQueue<TVertexType, TBatchDrawCommandData>(GraphicsDevice,
_commandDrawer,
maximumBatchCommandsCount);
}
/// <summary>
/// Gets the <see cref="GeometryBuffer{TVertexType}" /> associated with this
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}" />.
/// </summary>
/// <value>
/// The <see cref="GeometryBuffer{TVertexType}" /> associated with this
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}" />.
/// </value>
protected GeometryBuffer<TVertexType> GeometryBuffer { get; }
/// <summary>
/// Gets the <see cref="GraphicsDevice" /> associated with this
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}" />.
/// </summary>
/// <value>
/// The <see cref="GraphicsDevice" /> associated with this
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}" />.
/// </value>
public GraphicsDevice GraphicsDevice { get; }
/// <summary>
/// Gets a value indicating whether batching is currently in progress by being within a <see cref="Begin" /> and
/// <see cref="End" /> pair block of code.
/// </summary>
/// <value>
/// <c>true</c> if batching has begun; otherwise, <c>false</c>.
/// </value>
public bool HasBegun { get; private set; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="diposing">
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only
/// unmanaged resources.
/// </param>
protected virtual void Dispose(bool diposing)
{
// ReSharper disable once InvertIf
if (diposing)
{
GeometryBuffer?.Dispose();
_commandDrawer?.Dispose();
_commandDrawer = null;
_immediateCommandQueue.Dispose();
_immediateCommandQueue = null;
_deferredCommandQueue.Dispose();
_deferredCommandQueue = null;
}
}
[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.");
}
/// <summary>
/// Starts a group of geometry for rendering with the specified <see cref="Effect" />, <see cref="PrimitiveType" />,
/// and <see cref="BatchSortMode" />.
/// </summary>
/// <param name="effect">The <see cref="Effect" />.</param>
/// <param name="primitiveType">The <see cref="PrimitiveType" />.</param>
/// <param name="sortMode">The <see cref="BatchSortMode" />.</param>
/// <exception cref="ArgumentNullException"><paramref name="effect" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="sortMode" /> is not a valid <see cref="BatchSortMode" /> value or
/// <paramref name="primitiveType" /> is not a valid <see cref="PrimitiveType" /> value.
/// </exception>
/// <exception cref="InvalidOperationException">
/// <see cref="Begin" /> cannot be invoked again until <see cref="End" /> has been invoked.
/// </exception>
/// <remarks>
/// <para>
/// This method must be called before any enqueuing of draw calls. When all the geometry have been enqueued for
/// drawing, call <see cref="End" />.
/// </para>
/// </remarks>
protected void Begin(Effect effect, PrimitiveType primitiveType, BatchSortMode sortMode = BatchSortMode.Deferred)
{
if (effect == null)
throw new ArgumentNullException(nameof(effect));
if ((primitiveType < PrimitiveType.TriangleList) || (primitiveType > PrimitiveType.LineStrip))
throw new ArgumentOutOfRangeException(nameof(primitiveType));
EnsureHasNotBegun();
HasBegun = true;
if (sortMode != BatchSortMode.Immediate)
{
var deferredQueuer = _deferredCommandQueue;
deferredQueuer.SortMode = sortMode;
_currentCommandQueue = deferredQueuer;
}
else
{
_currentCommandQueue = _immediateCommandQueue;
}
_currentCommandQueue.Begin(effect, primitiveType);
}
/// <summary>
/// Ends and submits the group of geometry to the <see cref="GraphicsDevice" /> for rendering.
/// </summary>
/// <exception cref="InvalidOperationException">
/// <see cref="End" /> cannot be invoked until <see cref="Begin" /> has been invoked.
/// </exception>
/// <remarks>
/// <para>
/// This method must be called after all enqueuing of draw calls.
/// </para>
/// </remarks>
protected void End()
{
EnsureHasBegun();
HasBegun = false;
_currentCommandQueue.End();
}
/// <summary>
/// Submits the group of geometry to the <see cref="GraphicsDevice" /> for rendering without ending the group of
/// geometry.
/// </summary>
protected void Flush()
{
_currentCommandQueue.Flush();
}
/// <summary>
/// Adds geometry to the group of geometry for rendering using the specified primitive type, vertices, indices, data,
/// and optional sort key.
/// </summary>
/// <param name="startIndex">The starting index from the <see cref="GeometryBuffer" /> to use.</param>
/// <param name="primitiveCount">The number of primitives from the <see cref="GeometryBuffer" /> to use.</param>
/// <param name="sortKey">The sort key.</param>
/// <param name="itemData">The <see cref="TBatchDrawCommandData" />.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <remarks>
/// <para>
/// <see cref="Begin" /> must be called before enqueuing any draw calls. When all the geometry have been enqueued
/// for drawing, call <see cref="End" />.
/// </para>
/// </remarks>
protected void Draw(ushort startIndex, ushort primitiveCount, float sortKey, ref TBatchDrawCommandData itemData)
{
EnsureHasBegun();
_currentCommandQueue.EnqueueDrawCommand(startIndex, primitiveCount, sortKey, ref itemData);
}
}
}
@@ -0,0 +1,53 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
internal sealed class BatchCommandDrawer<TVertexType, TCommandData> : IDisposable
where TVertexType : struct, IVertexType where TCommandData : struct, IBatchDrawCommandData<TCommandData>
{
internal readonly GeometryBuffer<TVertexType> GeometryBuffer;
internal readonly GraphicsDevice GraphicsDevice;
internal Effect Effect;
internal PrimitiveType PrimitiveType;
internal BatchCommandDrawer(GraphicsDevice graphicsDevice, GeometryBuffer<TVertexType> geometryBuffer)
{
GraphicsDevice = graphicsDevice;
GeometryBuffer = geometryBuffer;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool isDisposing)
{
if (!isDisposing)
return;
GeometryBuffer.Dispose();
}
internal void SelectBuffers()
{
GeometryBuffer.Flush();
GraphicsDevice.SetVertexBuffer(GeometryBuffer.VertexBuffer);
GraphicsDevice.Indices = GeometryBuffer.IndexBuffer;
}
internal void Draw(ref BatchDrawCommand<TCommandData> command)
{
var graphicsDevice = GraphicsDevice;
command.Data.ApplyTo(Effect);
foreach (var pass in Effect.CurrentTechnique.Passes)
{
pass.Apply();
graphicsDevice.DrawIndexedPrimitives(PrimitiveType, 0, command.StartIndex,
command.PrimitiveCount);
}
command.Data.SetReferencesToNull();
}
}
}
@@ -0,0 +1,53 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
internal abstract class BatchCommandQueue<TVertexType, TCommandData> : IDisposable
where TVertexType : struct, IVertexType where TCommandData : struct, IBatchDrawCommandData<TCommandData>
{
internal BatchCommandDrawer<TVertexType, TCommandData> CommandDrawer;
internal GraphicsDevice GraphicsDevice;
internal PrimitiveType PrimitiveType;
protected BatchCommandQueue(GraphicsDevice graphicsDevice,
BatchCommandDrawer<TVertexType, TCommandData> commandDrawer)
{
GraphicsDevice = graphicsDevice;
CommandDrawer = commandDrawer;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
internal virtual void Begin(Effect effect, PrimitiveType primitiveType)
{
CommandDrawer.Effect = effect;
CommandDrawer.PrimitiveType = primitiveType;
PrimitiveType = primitiveType;
}
protected internal abstract void Flush();
internal void End()
{
Flush();
CommandDrawer.Effect = null;
}
internal abstract void EnqueueDrawCommand(ushort startIndex, ushort primitiveCount, float sortKey,
ref TCommandData data);
protected virtual void Dispose(bool isDisposing)
{
if (!isDisposing)
return;
// don't dispose the batch drawer here; it is a shared reference
CommandDrawer = null;
}
}
}
@@ -0,0 +1,20 @@
using System;
namespace MonoGame.Extended.Graphics.Batching
{
/// <summary>
/// The exception that is thrown when enqueuing draw commands into a
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}" />
/// results in an overflow.
/// </summary>
/// <seealso cref="Exception" />
public class BatchCommandQueueOverflowException : Exception
{
internal BatchCommandQueueOverflowException(int maximumCommandsCount)
: base(
$"The maximum number of batch commands ({maximumCommandsCount}) has been reached. Consider increasing the maximum, reducing the number of commands, or flushing the commands to the GraphicsDevice."
)
{
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.Runtime.InteropServices;
namespace MonoGame.Extended.Graphics.Batching
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct BatchDrawCommand<TCommandData> : IComparable<BatchDrawCommand<TCommandData>>
where TCommandData : struct, IBatchDrawCommandData<TCommandData>
{
internal float SortKey;
internal ushort StartIndex;
internal ushort PrimitiveCount;
internal TCommandData Data;
internal BatchDrawCommand(ushort startIndex, ushort primitiveCount, float sortKey, TCommandData data)
{
SortKey = sortKey;
StartIndex = startIndex;
PrimitiveCount = primitiveCount;
Data = data;
}
internal bool CanMergeWith(float sortKey, ref TCommandData commandData)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
return (SortKey == sortKey) && Data.Equals(ref commandData);
}
public int CompareTo(BatchDrawCommand<TCommandData> other)
{
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
var result = SortKey.CompareTo(other.SortKey);
if (result == 0)
result = Data.CompareTo(other.Data);
if (result == 0)
result = StartIndex.CompareTo(other.StartIndex);
return result;
}
}
}
@@ -0,0 +1,40 @@
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
/// <summary>
/// Defines options for how geometric objects are submitted to a <see cref="GraphicsDevice" />.
/// </summary>
public enum BatchSortMode : byte
{
/// <summary>
/// Each <see cref="Batch{TVertexType,TBatchDrawCommandData}.Draw" /> call invokes
/// <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> immediately for each
/// <see cref="EffectPass" />.
/// </summary>
Immediate,
/// <summary>
/// Each <see cref="Batch{TVertexType,TBatchDrawCommandData}.Draw" /> call is added to the end of an array of draw
/// commands. Draw commands will be merged, if possible, for batching. When
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}.Flush" /> or
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}.End" /> is called, the merged draw commands will be processed
/// on a first come, first serve, basis where each command invokes
/// <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> for each
/// <see cref="EffectPass" />.
/// </summary>
Deferred,
/// <summary>
/// Each <see cref="Batch{TVertexType,TBatchDrawCommandData}.Draw" /> call is added to the end of an array of draw
/// commands. Draw commands will be merged, if possible, for batching as are they called and after they are sorted in
/// ascending order by their sort keys. When
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}.Flush" /> or
/// <see cref="Batch{TVertexType,TBatchDrawCommandData}.End" /> is called, the sorting takes place and then the merged
/// draw commands are processed where each command invokes
/// <see cref="GraphicsDevice.DrawIndexedPrimitives(PrimitiveType, int, int, int)" /> for each
/// <see cref="EffectPass" />.
/// </summary>
DeferredSorted
}
}
@@ -0,0 +1,160 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
internal sealed class DeferredBatchCommandQueue<TVertexType, TCommandData> :
BatchCommandQueue<TVertexType, TCommandData>
where TVertexType : struct, IVertexType where TCommandData : struct, IBatchDrawCommandData<TCommandData>
{
private readonly BatchDrawCommand<TCommandData>[] _commands;
private readonly GeometryBuffer<TVertexType> _geometryBuffer;
private readonly int _maximumCommandsCount;
private readonly ushort[] _sortedIndices;
private int _commandsCount;
private BatchDrawCommand<TCommandData> _currentCommand;
internal BatchSortMode SortMode;
internal DeferredBatchCommandQueue(GraphicsDevice graphicsDevice,
BatchCommandDrawer<TVertexType, TCommandData> commandDrawer, int maximumCommandsCount)
: base(graphicsDevice, commandDrawer)
{
_commands = new BatchDrawCommand<TCommandData>[maximumCommandsCount];
_geometryBuffer = commandDrawer.GeometryBuffer;
_sortedIndices = new ushort[_geometryBuffer._indices.Length];
_maximumCommandsCount = maximumCommandsCount;
}
protected internal override void Flush()
{
// quit early if there are no deferred commands to process
if (_commandsCount < 0)
return;
var vertexBuffer = _geometryBuffer.VertexBuffer;
// upload the vertices to the GPU's memory from CPU's memory
vertexBuffer.SetData(_geometryBuffer._vertices, 0, _geometryBuffer._vertexCount);
// tell the graphics API we want to use that buffer of vertices for rendering
GraphicsDevice.SetVertexBuffer(vertexBuffer);
// conditional code depending on deferred mode
// ReSharper disable once SwitchStatementMissingSomeCases
switch (SortMode)
{
case BatchSortMode.Deferred:
FlushDeferred();
break;
case BatchSortMode.DeferredSorted:
FlushDeferredSorted();
break;
}
// prevent a highly unlikely, but possible, memory leak where the current command would be holding onto some reference(s)
_currentCommand.Data.SetReferencesToNull();
// clear the geometry buffer if it is dynamic
if (_geometryBuffer.BufferType == GeometryBufferType.Dynamic)
_geometryBuffer.Clear();
// reset the commands count
// we don't clear the array of commands because we will just overwrite the values anyways
_commandsCount = 0;
}
private void FlushDeferred()
{
var indexBuffer = _geometryBuffer.IndexBuffer;
// upload the indices to the GPU's memory from CPU's memory
indexBuffer.SetData(_geometryBuffer._indices, 0, _geometryBuffer._indexCount);
// tell the graphics API we want to use that buffer of indices for rendering
GraphicsDevice.Indices = indexBuffer;
// ReSharper disable once ForCanBeConvertedToForeach
for (var index = 0; index < _commandsCount; index++)
CommandDrawer.Draw(ref _commands[index]);
}
private void FlushDeferredSorted()
{
// sort the commands
Array.Sort(_commands, 0, _commandsCount);
_currentCommand = _commands[0];
ushort sortedIndiesCount = 0;
var newCommandsCount = 1;
// change the indices used by the command to use the sorted indices array so the indices are sequential for the graphics API
_commands[0].StartIndex = 0;
// get the number of vertices used for the current command, each index represents a vertex
var commandIndicesCount = PrimitiveType.GetVerticesCount(_currentCommand.PrimitiveCount);
// copy the indices of the current command into our sorted indices array so the indices are sequential for the graphics API
Array.Copy(_geometryBuffer._indices, _currentCommand.StartIndex, _sortedIndices, sortedIndiesCount,
commandIndicesCount);
sortedIndiesCount += (ushort) commandIndicesCount;
// iterate through sorted commands checking if any can now be merged to reduce expensive draw calls to the graphics API
// this might need to be changed for next-gen graphics API (Vulkan, Metal, DirectX 11) where the draw calls are not so expensive
for (var index = 1; index < _commandsCount; index++)
{
var command = _commands[index];
// get the number of vertices used for the command, each index represents a vertex
commandIndicesCount = PrimitiveType.GetVerticesCount(command.PrimitiveCount);
if (_currentCommand.CanMergeWith(command.SortKey, ref command.Data))
{
// increase the number of primitives for the current command by the amount of the merged command
_commands[newCommandsCount - 1].PrimitiveCount =
_currentCommand.PrimitiveCount += command.PrimitiveCount;
}
else
{
// could not merge command
newCommandsCount++;
// change the indices used by the command to use the sorted indices array so the indices are sequential for the graphics API
_commands[newCommandsCount - 1].StartIndex = sortedIndiesCount;
// set the current command to this command so we can check if the next command can be merged
_currentCommand = command;
}
// copy the indices of the command into our sorted indices array so the merged indices are sequential for the graphics API
Array.Copy(_geometryBuffer._indices, command.StartIndex, _sortedIndices, sortedIndiesCount,
commandIndicesCount);
sortedIndiesCount += (ushort) commandIndicesCount;
}
var indexBuffer = _geometryBuffer.IndexBuffer;
// upload the indices to the GPU's memory from CPU's memory
indexBuffer.SetData(_sortedIndices, 0, sortedIndiesCount);
// tell the graphics API we want to use that buffer of indices for rendering
GraphicsDevice.Indices = indexBuffer;
// ReSharper disable once ForCanBeConvertedToForeach
for (var index = 0; index < newCommandsCount; index++)
CommandDrawer.Draw(ref _commands[index]);
}
internal override void EnqueueDrawCommand(ushort startIndex, ushort primitiveCount, float sortKey,
ref TCommandData data)
{
// merge draw commands if possible to reduce expensive draw calls to the graphics API
// this might need to be changed for next-gen graphics API (Vulkan, Metal, DirectX 11) where the draw calls are not so expensive
if (_currentCommand.CanMergeWith(sortKey, ref data))
{
// since indices are added in sequential fasion we can just increase the primitive count of the current command
_commands[_commandsCount - 1].PrimitiveCount = _currentCommand.PrimitiveCount += primitiveCount;
return;
}
// overflow check
if (_commandsCount >= _maximumCommandsCount)
throw new BatchCommandQueueOverflowException(_maximumCommandsCount);
// could not merge draw command, initialize a new one
_currentCommand = new BatchDrawCommand<TCommandData>(startIndex, primitiveCount, sortKey, data);
// append the command to the array
_commands[_commandsCount++] = _currentCommand;
}
}
}
@@ -0,0 +1,25 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
/// <summary>
/// Defines the data for batch draw commands.
/// </summary>
/// <typeparam name="TCommandData">The type of the batch draw command data.</typeparam>
/// <seealso cref="Extended.IEquatableByRef{TBatchDrawCommandData}" />
public interface IBatchDrawCommandData<TCommandData> : IEquatableByRef<TCommandData>, IComparable<TCommandData>
{
/// <summary>
/// Applies the parameters for the <see cref="IBatchDrawCommandData{TBatchDrawCommandData}" /> to the specified
/// <see cref="Effect" />.
/// </summary>
/// <param name="effect">The effect.</param>
void ApplyTo(Effect effect);
/// <summary>
/// Sets all the associated references to <code>null</code>.
/// </summary>
void SetReferencesToNull();
}
}
@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Batching
{
internal sealed class ImmediateBatchCommandQueue<TVertexType, TCommandData> :
BatchCommandQueue<TVertexType, TCommandData>
where TVertexType : struct, IVertexType
where TCommandData : struct, IBatchDrawCommandData<TCommandData>
{
public ImmediateBatchCommandQueue(GraphicsDevice graphicsDevice,
BatchCommandDrawer<TVertexType, TCommandData> batchCommandDrawer)
: base(graphicsDevice, batchCommandDrawer)
{
}
protected internal override void Flush()
{
}
internal override void EnqueueDrawCommand(ushort startIndex, ushort primitiveCount, float sortKey,
ref TCommandData data)
{
CommandDrawer.SelectBuffers();
var command = new BatchDrawCommand<TCommandData>(startIndex, primitiveCount, sortKey, data);
CommandDrawer.Draw(ref command);
CommandDrawer.GeometryBuffer.Clear();
}
}
}
@@ -0,0 +1,785 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Graphics.Batching;
using MonoGame.Extended.Graphics.Effects;
namespace MonoGame.Extended.Graphics
{
/// <summary>
/// Enables a group of dynamic two-dimensional geometric objects be batched together for rendering, if possible, using
/// the same settings.
/// </summary>
/// <seealso cref="Batch{TVertexType,TBatchDrawCommandData}" />
public class DynamicBatch2D : Batch<VertexPositionColorTexture, DynamicBatch2D.DrawCommandData>
{
internal const int DefaultMaximumVerticesCount = 8192;
internal const int DefaultMaximumIndicesCount = 12288;
private readonly DefaultEffect2D _defaultEffect;
private readonly Texture2D _pixelTexture;
private BlendState _blendState;
private DepthStencilState _depthStencilState;
private Effect _effect;
private DrawCommandData _pixelTextureDrawContext;
private Matrix? _projectionMatrix;
private RasterizerState _rasterizerState;
private SamplerState _samplerState;
private Batch2DSortMode _sortMode;
private Matrix _viewMatrix;
private Matrix _worldMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicBatch2D" /> class.
/// </summary>
/// <param name="graphicsDevice">The graphics device.</param>
/// <param name="maximumVerticesCount">The maximum number of vertices. The default value is <code>8192</code>.</param>
/// <param name="maximumIndicesCount">The maximum number of indices. The default value is <code>12288</code>.</param>
/// <param name="maximumBatchCommandsCount">
/// The maximum number of draw calls that can be deferred before they have to be
/// submitted to the <see cref="GraphicsDevice" />.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="graphicsDevice" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="maximumBatchCommandsCount" /> is less than or equal
/// <code>0</code>, or <paramref name="maximumVerticesCount" /> is less than or equal to <code>0</code>, or,
/// <paramref name="maximumVerticesCount" /> is less than or equal to <code>0</code>.
/// </exception>
public DynamicBatch2D(GraphicsDevice graphicsDevice, ushort maximumVerticesCount = DefaultMaximumVerticesCount,
ushort maximumIndicesCount = DefaultMaximumIndicesCount,
int maximumBatchCommandsCount = DefaultMaximumBatchCommandsCount)
: base(
new DynamicGeometryBuffer<VertexPositionColorTexture>(graphicsDevice, maximumVerticesCount,
maximumIndicesCount), maximumBatchCommandsCount)
{
_defaultEffect = new DefaultEffect2D(graphicsDevice);
_pixelTexture = new Texture2D(graphicsDevice, 1, 1);
_pixelTexture.SetData(new[]
{
Color.White
});
_pixelTextureDrawContext = new DrawCommandData
{
Texture = _pixelTexture
};
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing">
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only
/// unmanaged resources.
/// </param>
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
_pixelTextureDrawContext.Texture = null;
_pixelTexture?.Dispose();
}
/// <summary>
/// Starts a group of two-dimensional geometry for rendering with the specified <see cref="BatchSortMode" />,
/// <see cref="Effect" /> and the optional chain of <see cref="Matrix" />es for transforming between world, view, and
/// projection spaces.
/// </summary>
/// <param name="sortMode">The <see cref="Batch2DSortMode" />. Default value is <see cref="Batch2DSortMode.Deferred" />.</param>
/// <param name="blendState">
/// The <see cref="BlendState" />. Use <code>null</code> to use the default
/// <see cref="BlendState.AlphaBlend" />.
/// </param>
/// <param name="samplerState">
/// The <see cref="SamplerState" />. Use <code>null</code> to use the default
/// <see cref="SamplerState.LinearClamp" />.
/// </param>
/// <param name="depthStencilState">
/// The <see cref="DepthStencilState" />. Use <code>null</code> to use the default
/// <see cref="DepthStencilState.None" />.
/// </param>
/// <param name="rasterizerState">
/// The <see cref="RasterizerState" />. Use <code>null</code> to use the default
/// <see cref="RasterizerState.CullCounterClockwise" />.
/// </param>
/// <param name="effect">
/// The <see cref="Effect" />. Use <code>null</code> to use the default <see cref="DefaultEffect2D" />.
/// </param>
/// <param name="worldMatrix">
/// The <see cref="Matrix" /> to transform vertices from a common model-space to world-space. Use <code>null</code> to
/// use <see cref="Matrix.Identity" />.
/// </param>
/// <param name="viewMatrix">
/// The <see cref="Matrix" /> to transform vertices from the world-space to view-space. Use <code>null</code> to
/// use <see cref="Matrix.Identity" />.
/// </param>
/// <param name="projectionMatrix">
/// The <see cref="Matrix" /> to transform vertices from the view-space to projection-space. Use <code>null</code> to
/// use an orthographic projection (a 2D projection) with <code>(0,0)</code> as the top-left and <code>(x,y)</code> as
/// the bottom-right of the viewport.
/// </param>
public void Begin(Batch2DSortMode sortMode = Batch2DSortMode.Deferred, BlendState blendState = null,
SamplerState samplerState = null, DepthStencilState depthStencilState = null,
RasterizerState rasterizerState = null, Effect effect = null,
Matrix? worldMatrix = null,
Matrix? viewMatrix = null, Matrix? projectionMatrix = null)
{
_blendState = blendState ?? BlendState.AlphaBlend;
_samplerState = samplerState ?? SamplerState.LinearClamp;
_depthStencilState = depthStencilState ?? DepthStencilState.None;
_rasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise;
_effect = effect ?? _defaultEffect;
_worldMatrix = worldMatrix ?? Matrix.Identity;
_viewMatrix = viewMatrix ?? Matrix.Identity;
_projectionMatrix = projectionMatrix;
BatchSortMode batchSortMode;
switch (sortMode)
{
case Batch2DSortMode.Texture:
case Batch2DSortMode.FrontToBack:
case Batch2DSortMode.BackToFront:
batchSortMode = BatchSortMode.DeferredSorted;
break;
case Batch2DSortMode.Deferred:
batchSortMode = BatchSortMode.Deferred;
break;
case Batch2DSortMode.Immediate:
batchSortMode = BatchSortMode.Immediate;
ApplyStates();
break;
default:
throw new ArgumentOutOfRangeException(nameof(sortMode), sortMode, null);
}
_sortMode = sortMode;
Begin(_effect, PrimitiveType.TriangleList, batchSortMode);
}
/// <summary>
/// Ends and submits the group of geometry to the
/// <see cref="P:MonoGame.Extended.Graphics.Batching.Batch`2.GraphicsDevice" /> for rendering.
/// </summary>
/// <remarks>
/// This method must be called after all enqueuing of draw calls.
/// </remarks>
public new void End()
{
if (_sortMode != Batch2DSortMode.Immediate)
ApplyStates();
base.End();
}
private void ApplyStates()
{
var graphicsDevice = GraphicsDevice;
graphicsDevice.BlendState = _blendState;
graphicsDevice.SamplerStates[0] = _samplerState;
graphicsDevice.DepthStencilState = _depthStencilState;
graphicsDevice.RasterizerState = _rasterizerState;
var matrixChainEffect = _effect as IMatrixChainEffect;
if (matrixChainEffect == null)
return;
matrixChainEffect.SetWorld(ref _worldMatrix);
matrixChainEffect.SetView(ref _viewMatrix);
if (_projectionMatrix.HasValue)
{
matrixChainEffect.Projection = _projectionMatrix.Value;
}
else
{
var viewport = GraphicsDevice.Viewport;
var projectionOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);
var projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, -1);
Matrix.Multiply(ref projectionOffset, ref projection, out projection);
matrixChainEffect.SetProjection(ref projection);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float GetSortKey(float depth)
{
// the sort key acts as the primary sort key
// the secondary sort key is always the texture
// ReSharper disable once SwitchStatementMissingSomeCases
switch (_sortMode)
{
case Batch2DSortMode.FrontToBack:
return depth;
case Batch2DSortMode.BackToFront:
return -depth;
default:
return 0;
}
}
/// <summary>
/// Draws a sprite using a specified <see cref="Texture" />, transform <see cref="Matrix2D" /> and an optional
/// source <see cref="Rectangle" />, <see cref="Color" />, origin <see cref="Vector2" />,
/// <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="texture">The <see cref="Texture" />.</param>
/// <param name="destinationRectangle">
/// The destination <see cref="Rectangle" /> that specifies the world destination for
/// drawing the sprite. If this rectangle is not the same size as the <paramref name="sourceRectangle" />, the sprite
/// will be scaled to fit.
/// </param>
/// <param name="sourceRectangle">
/// The texture region <see cref="Rectangle" /> of the <paramref name="texture" />. Use
/// <code>null</code> to use the entire <see cref="Texture2D" />.
/// </param>
/// <param name="color">The <see cref="Color" />. Use <code>null</code> to use the default <see cref="Color.White" />.</param>
/// <param name="rotation">
/// The angle <see cref="float" /> (in radians) to rotate the sprite about its <paramref name="origin" />. The default
/// value is <code>0f</code>.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0</code>.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="texture" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
[Obsolete("The Draw method is deprecated, please use the DrawSprite method instead.")]
public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle = null,
Color? color = null, float rotation = 0f, Vector2? origin = null, SpriteEffects effects = SpriteEffects.None,
float depth = 0)
{
DrawSprite(texture, destinationRectangle, sourceRectangle, color, rotation, origin, effects, depth);
}
/// <summary>
/// Draws a sprite using a specified <see cref="Texture" />, transform <see cref="Matrix2D" /> and an optional
/// source <see cref="Rectangle" />, <see cref="Color" />, origin <see cref="Vector2" />,
/// <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="texture">The <see cref="Texture" />.</param>
/// <param name="destinationRectangle">
/// The destination <see cref="Rectangle" /> that specifies the world destination for
/// drawing the sprite. If this rectangle is not the same size as the <paramref name="sourceRectangle" />, the sprite
/// will be scaled to fit.
/// </param>
/// <param name="sourceRectangle">
/// The texture region <see cref="Rectangle" /> of the <paramref name="texture" />. Use
/// <code>null</code> to use the entire <see cref="Texture2D" />.
/// </param>
/// <param name="color">The <see cref="Color" />. Use <code>null</code> to use the default <see cref="Color.White" />.</param>
/// <param name="rotation">
/// The angle <see cref="float" /> (in radians) to rotate the sprite about its <paramref name="origin" />. The default
/// value is <code>0f</code>.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0</code>.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="texture" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawSprite(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle = null,
Color? color = null, float rotation = 0f, Vector2? origin = null, SpriteEffects effects = SpriteEffects.None,
float depth = 0)
{
var position = new Vector2(destinationRectangle.X, destinationRectangle.Y);
var size = new Size(destinationRectangle.Width, destinationRectangle.Height);
Matrix2D transformMatrix;
CalculateTransformMatrix(position, rotation, null, out transformMatrix);
var geometryBuffer = GeometryBuffer;
var startVertex = geometryBuffer._vertexCount;
var startIndex = geometryBuffer._indexCount;
geometryBuffer.EnqueueSprite(startVertex, texture, ref transformMatrix, sourceRectangle, size, color, origin,
effects, depth);
var commandData = new DrawCommandData(texture);
var sortKey = GetSortKey(depth);
Draw(startIndex, 2, sortKey, ref commandData);
}
/// <summary>
/// Draws a sprite using a specified <see cref="Texture" />, transform <see cref="Matrix2D" /> and an optional
/// source <see cref="Rectangle" />, <see cref="Color" />, origin <see cref="Vector2" />,
/// <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="texture">The <see cref="Texture" />.</param>
/// <param name="transformMatrix">The transform <see cref="Matrix2D" />.</param>
/// <param name="sourceRectangle">
/// The texture region <see cref="Rectangle" /> of the <paramref name="texture" />. Use
/// <code>null</code> to use the entire <see cref="Texture2D" />.
/// </param>
/// <param name="color">The <see cref="Color" />. Use <code>null</code> to use the default <see cref="Color.White" />.</param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0</code>.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="texture" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawSprite(Texture2D texture, ref Matrix2D transformMatrix, Rectangle? sourceRectangle = null,
Color? color = null, Vector2? origin = null, SpriteEffects effects = SpriteEffects.None,
float depth = 0)
{
var geometryBuffer = GeometryBuffer;
var startVertex = geometryBuffer._vertexCount;
var startIndex = geometryBuffer._indexCount;
geometryBuffer.EnqueueSprite(startVertex, texture, ref transformMatrix, sourceRectangle, null, color, origin,
effects, depth);
var commandData = new DrawCommandData(texture);
var sortKey = GetSortKey(depth);
Draw(startIndex, 2, sortKey, ref commandData);
}
/// <summary>
/// Draws a sprite using a specified <see cref="Texture" />, position <see cref="Vector2" /> and an optional source
/// <see cref="Rectangle" />, <see cref="Color" />, rotation <see cref="float" />, origin <see cref="Vector2" />, scale
/// <see cref="Vector2" />, <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="texture">The <see cref="Texture" />.</param>
/// <param name="position">The world position <see cref="Vector2" />.</param>
/// <param name="sourceRectangle">
/// The texture region <see cref="Rectangle" /> of the <paramref name="texture" />. Use
/// <code>null</code> to use the entire <see cref="Texture2D" />.
/// </param>
/// <param name="color">The <see cref="Color" />. Use <code>null</code> to use the default <see cref="Color.White" />.</param>
/// <param name="rotation">
/// The angle <see cref="float" /> (in radians) to rotate the sprite about its <paramref name="origin" />. The default
/// value is <code>0f</code>.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="scale">
/// The scale <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.One" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0f</code>.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="texture" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawSprite(Texture2D texture, Vector2 position, Rectangle? sourceRectangle = null,
Color? color = null, float rotation = 0f, Vector2? origin = null, Vector2? scale = null,
SpriteEffects effects = SpriteEffects.None,
float depth = 0)
{
Matrix2D transformMatrix;
CalculateTransformMatrix(position, rotation, scale, out transformMatrix);
DrawSprite(texture, ref transformMatrix, sourceRectangle, color, origin, effects, depth);
}
/// <summary>
/// Draws a sprite using a specified <see cref="Texture" />, position <see cref="Vector2" /> and an optional source
/// <see cref="Rectangle" />, <see cref="Color" />, rotation <see cref="float" />, origin <see cref="Vector2" />, scale
/// <see cref="Vector2" />, <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="texture">The <see cref="Texture" />.</param>
/// <param name="position">The world position <see cref="Vector2" />.</param>
/// <param name="sourceRectangle">
/// The texture region <see cref="Rectangle" /> of the <paramref name="texture" />. Use
/// <code>null</code> to use the entire <see cref="Texture2D" />.
/// </param>
/// <param name="color">The <see cref="Color" />. Use <code>null</code> to use the default <see cref="Color.White" />.</param>
/// <param name="rotation">
/// The angle <see cref="float" /> (in radians) to rotate the sprite about its <paramref name="origin" />. The default
/// value is <code>0f</code>.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="scale">
/// The scale <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.One" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0f</code>.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="texture" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
[Obsolete("The Draw method is deprecated, please use the DrawSprite method instead.")]
public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle = null,
Color? color = null, float rotation = 0f, Vector2? origin = null, Vector2? scale = null,
SpriteEffects effects = SpriteEffects.None,
float depth = 0)
{
DrawSprite(texture, position, sourceRectangle, color, rotation, origin, scale, effects, depth);
}
/// <summary>
/// Draws unicode (UTF-16) characters as sprites using the specified <see cref="BitmapFont" />, text
/// <see cref="StringBuilder" />, transform <see cref="Matrix2D" /> and optional <see cref="Color" />, origin
/// <see cref="Vector2" />, <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="bitmapFont">The <see cref="BitmapFont" />.</param>
/// <param name="text">The text <see cref="StringBuilder" />.</param>
/// <param name="transformMatrix">The transform <see cref="Matrix2D" />.</param>
/// <param name="color">
/// The <see cref="Color" />. Use <code>null</code> to use the default
/// <see cref="Color.White" />.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0f</code>.</param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="bitmapFont" /> is null or <paramref name="text" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawString(BitmapFont bitmapFont, StringBuilder text, ref Matrix2D transformMatrix,
Color? color = null,
Vector2? origin = null, SpriteEffects effects = SpriteEffects.None, float depth = 0f)
{
EnsureHasBegun();
if (bitmapFont == null)
throw new ArgumentNullException(nameof(bitmapFont));
if (text == null)
throw new ArgumentNullException(nameof(text));
var lineSpacing = bitmapFont.LineHeight;
var offset = new Vector2(0, 0);
for (var i = 0; i < text.Length;)
{
int character;
if (char.IsLowSurrogate(text[i]))
{
character = char.ConvertToUtf32(text[i - 1], text[i]);
i += 2;
}
else if (char.IsHighSurrogate(text[i]))
{
character = char.ConvertToUtf32(text[i], text[i - 1]);
i += 2;
}
else
{
character = text[i];
i += 1;
}
// ReSharper disable once SwitchStatementMissingSomeCases
switch (character)
{
case '\r':
continue;
case '\n':
offset.X = 0;
offset.Y += lineSpacing;
continue;
}
var fontRegion = bitmapFont.GetCharacterRegion(character);
if (fontRegion == null)
continue;
var transform1Matrix = transformMatrix;
transform1Matrix.M31 += offset.X + fontRegion.XOffset;
transform1Matrix.M32 += offset.Y + fontRegion.YOffset;
var textureRegion = fontRegion.TextureRegion;
var bounds = textureRegion.Bounds;
DrawSprite(textureRegion.Texture, ref transform1Matrix, bounds, color, origin, effects, depth);
offset.X += i != text.Length - 1
? fontRegion.XAdvance + bitmapFont.LetterSpacing
: fontRegion.XOffset + fontRegion.Width;
}
}
/// <summary>
/// Draws unicode (UTF-16) characters as sprites using the specified <see cref="BitmapFont" />, text
/// <see cref="StringBuilder" />, position <see cref="Vector2" /> and optional <see cref="Color" />, rotation
/// <see cref="float" />, origin <see cref="Vector2" />, scale <see cref="Vector2" /> <see cref="SpriteEffects" />, and
/// depth <see cref="float" />.
/// </summary>
/// <param name="bitmapFont">The <see cref="BitmapFont" />.</param>
/// <param name="text">The text <see cref="string" />.</param>
/// <param name="position">The position <see cref="Vector2" />.</param>
/// <param name="color">
/// The <see cref="Color" />. Use <code>null</code> to use the default
/// <see cref="Color.White" />.
/// </param>
/// <param name="rotation">
/// The angle <see cref="float" /> (in radians) to rotate each sprite about its <paramref name="origin" />. The default
/// value is <code>0f</code>.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="scale">
/// The scale <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.One" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0f</code></param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="bitmapFont" /> is null or <paramref name="text" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawString(BitmapFont bitmapFont, StringBuilder text, Vector2 position, Color? color = null,
float rotation = 0f, Vector2? origin = null, Vector2? scale = null,
SpriteEffects effects = SpriteEffects.None, float depth = 0f)
{
Matrix2D transformMatrix;
CalculateTransformMatrix(position, rotation, scale, out transformMatrix);
DrawString(bitmapFont, text, ref transformMatrix, color, origin, effects, depth);
}
/// <summary>
/// Draws unicode (UTF-16) characters as sprites using the specified <see cref="BitmapFont" />, text
/// <see cref="string" />, transform <see cref="Matrix2D" /> and optional <see cref="Color" />, origin
/// <see cref="Vector2" />, <see cref="SpriteEffects" />, and depth <see cref="float" />.
/// </summary>
/// <param name="bitmapFont">The <see cref="BitmapFont" />.</param>
/// <param name="text">The text <see cref="string" />.</param>
/// <param name="transformMatrix">The transform <see cref="Matrix2D" />.</param>
/// <param name="color">
/// The <see cref="Color" />. Use <code>null</code> to use the default
/// <see cref="Color.White" />.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0f</code></param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="bitmapFont" /> is null or <paramref name="text" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawString(BitmapFont bitmapFont, string text, ref Matrix2D transformMatrix, Color? color = null,
Vector2? origin = null, SpriteEffects effects = SpriteEffects.None, float depth = 0f)
{
EnsureHasBegun();
if (bitmapFont == null)
throw new ArgumentNullException(nameof(bitmapFont));
if (text == null)
throw new ArgumentNullException(nameof(text));
var lineSpacing = bitmapFont.LineHeight;
var offset = new Vector2(0, 0);
for (var i = 0; i < text.Length;)
{
int character;
if (char.IsLowSurrogate(text[i]))
{
character = char.ConvertToUtf32(text[i - 1], text[i]);
i += 2;
}
else if (char.IsHighSurrogate(text[i]))
{
character = char.ConvertToUtf32(text[i], text[i - 1]);
i += 2;
}
else
{
character = text[i];
i += 1;
}
// ReSharper disable once SwitchStatementMissingSomeCases
switch (character)
{
case '\r':
continue;
case '\n':
offset.X = 0;
offset.Y += lineSpacing;
continue;
}
var fontRegion = bitmapFont.GetCharacterRegion(character);
if (fontRegion == null)
continue;
var transform1Matrix = transformMatrix;
transform1Matrix.M31 += offset.X + fontRegion.XOffset;
transform1Matrix.M32 += offset.Y + fontRegion.YOffset;
var textureRegion = fontRegion.TextureRegion;
var bounds = textureRegion.Bounds;
DrawSprite(textureRegion.Texture, ref transform1Matrix, bounds, color, origin, effects, depth);
offset.X += i != text.Length - 1
? fontRegion.XAdvance + bitmapFont.LetterSpacing
: fontRegion.XOffset + fontRegion.Width;
}
}
/// <summary>
/// Draws unicode (UTF-16) characters as sprites using the specified <see cref="BitmapFont" />, text
/// <see cref="string" />, position <see cref="Vector2" /> and optional <see cref="Color" />, rotation
/// <see cref="float" />, origin <see cref="Vector2" />, scale <see cref="Vector2" /> <see cref="SpriteEffects" />, and
/// depth <see cref="float" />.
/// </summary>
/// <param name="bitmapFont">The <see cref="BitmapFont" />.</param>
/// <param name="text">The text <see cref="string" />.</param>
/// <param name="position">The position <see cref="Vector2" />.</param>
/// <param name="color">
/// The <see cref="Color" />. Use <code>null</code> to use the default
/// <see cref="Color.White" />.
/// </param>
/// <param name="rotation">
/// The angle <see cref="float" /> (in radians) to rotate each sprite about its <paramref name="origin" />. The default
/// value is <code>0f</code>.
/// </param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="scale">
/// The scale <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.One" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth <see cref="float" />. The default value is <code>0f</code></param>
/// <exception cref="InvalidOperationException">The <see cref="Begin" /> method has not been called.</exception>
/// <exception cref="ArgumentNullException"><paramref name="bitmapFont" /> is null or <paramref name="text" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{VertexPositionColorTexture}">
/// The underlying
/// <see cref="GeometryBuffer{VertexPositionColorTexture}" /> is full.
/// </exception>
/// <exception cref="BatchCommandQueueOverflowException">The batch command queue is full.</exception>
public void DrawString(BitmapFont bitmapFont, string text, Vector2 position, Color? color = null,
float rotation = 0f, Vector2? origin = null, Vector2? scale = null,
SpriteEffects effects = SpriteEffects.None, float depth = 0f)
{
var matrix = Matrix2D.Identity;
if (scale.HasValue)
{
var scaleMatrix = Matrix2D.CreateScale(scale.Value);
Matrix2D.Multiply(ref matrix, ref scaleMatrix, out matrix);
}
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (rotation != 0f)
{
var rotationMatrix = Matrix2D.CreateRotationZ(-rotation);
Matrix2D.Multiply(ref matrix, ref rotationMatrix, out matrix);
}
var translationMatrix = Matrix2D.CreateTranslation(position);
Matrix2D.Multiply(ref matrix, ref translationMatrix, out matrix);
DrawString(bitmapFont, text, ref matrix, color, origin, effects, depth);
}
private static void CalculateTransformMatrix(Vector2 position, float rotation, Vector2? scale,
out Matrix2D transformMatrix)
{
transformMatrix = Matrix2D.Identity;
if (scale.HasValue)
{
var scaleMatrix = Matrix2D.CreateScale(scale.Value);
Matrix2D.Multiply(ref transformMatrix, ref scaleMatrix, out transformMatrix);
}
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (rotation != 0f)
{
var rotationMatrix = Matrix2D.CreateRotationZ(-rotation);
Matrix2D.Multiply(ref transformMatrix, ref rotationMatrix, out transformMatrix);
}
var translationMatrix = Matrix2D.CreateTranslation(position);
Matrix2D.Multiply(ref transformMatrix, ref translationMatrix, out transformMatrix);
}
/// <summary>
/// Defines a drawing context for two-dimensional geometric objects.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[EditorBrowsable(EditorBrowsableState.Never)]
public struct DrawCommandData : IBatchDrawCommandData<DrawCommandData>
{
public Texture2D Texture;
public int TextureKey;
internal DrawCommandData(Texture2D texture)
{
Texture = texture;
TextureKey = RuntimeHelpers.GetHashCode(texture);
}
public void ApplyTo(Effect effect)
{
var textureEffect = effect as ITexture2DEffect;
if (textureEffect != null)
textureEffect.Texture = Texture;
}
public void SetReferencesToNull()
{
Texture = null;
}
public bool Equals(ref DrawCommandData other)
{
return Texture == other.Texture;
}
public int CompareTo(DrawCommandData other)
{
return TextureKey.CompareTo(other.TextureKey);
}
}
}
}
@@ -0,0 +1,74 @@
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Effects
{
/// <summary>
/// An <see cref="Effect" /> that allows 2D objects, within a 3D context, to be represented on a 2D monitor.
/// </summary>
/// <seealso cref="Effect" />
/// <seealso cref="IMatrixChainEffect" />
public class DefaultEffect2D : MatrixChainEffect, ITexture2DEffect
{
private Texture2D _texture;
private bool _textureIsDirty;
private EffectParameter _textureParameter;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultEffect2D" /> class.
/// </summary>
/// <param name="graphicsDevice">The graphics device.</param>
public DefaultEffect2D(GraphicsDevice graphicsDevice)
: base(graphicsDevice, EffectResource.DefaultEffect2D.Bytecode)
{
CacheEffectParameters();
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultEffect2D" /> class.
/// </summary>
/// <param name="cloneSource">The clone source.</param>
public DefaultEffect2D(Effect cloneSource)
: base(cloneSource)
{
CacheEffectParameters();
}
/// <summary>
/// Gets or sets the <see cref="Texture2D" />.
/// </summary>
/// <value>
/// The <see cref="Texture2D" />.
/// </value>
public Texture2D Texture
{
get { return _texture; }
set
{
_texture = value;
_textureIsDirty = true;
}
}
private void CacheEffectParameters()
{
_textureParameter = Parameters["TextureSampler+Texture"];
}
/// <summary>
/// Computes derived parameter values immediately before applying the effect.
/// </summary>
protected override bool OnApply()
{
var result = base.OnApply();
// ReSharper disable once InvertIf
if (_textureIsDirty)
{
_textureParameter.SetValue(_texture);
_textureIsDirty = false;
}
return result;
}
}
}
@@ -0,0 +1,75 @@
using System.IO;
using System.Reflection;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Effects
{
/// <summary>
/// Reperesents the bytecode of an <see cref="Effect" /> that is encapsulated inside a compiled assembly.
/// </summary>
/// <remarks>
/// <para>
/// Files that are encapsulated inside a compiled assembly are commonly known as Manifiest or embedded resources.
/// Since embedded resources are added to the assembly at compiled time, they can not be accidentally deleted or
/// misplaced. However, if the file needs to be changed, the assembly will need to be re-compiled with the changed
/// file.
/// </para>
/// <para>
/// To add an embedded resource file to an assembly, first add it to the project and then change the Build Action
/// in the Properties of the file to <code>Embedded Resource</code>. The next time the project is compiled, the
/// compiler will add the file to the assembly as an embedded resource. The compiler adds namespace(s) to the
/// embedded resource so it matches with the path of where the file was added to the project.
/// </para>
/// </remarks>
public class EffectResource
{
/// <summary>
/// The <see cref="Effects.DefaultEffect2D" /> embedded into the MonoGame.Extended library.
/// </summary>
public static readonly EffectResource DefaultEffect2D =
new EffectResource("MonoGame.Extended.Graphics.Effects.Resources.DefaultEffect2D.mgfxo");
private readonly string _resourceName;
private volatile byte[] _bytecode;
/// <summary>
/// Initializes a new instance of the <see cref="EffectResource" /> class.
/// </summary>
/// <param name="resourceName">The name of the embedded resource. This must include the namespace(s).</param>
public EffectResource(string resourceName)
{
_resourceName = resourceName;
}
/// <summary>
/// Gets the bytecode of the <see cref="Effect" /> file.
/// </summary>
/// <value>
/// The bytecode of the <see cref="Effect" /> file.
/// </value>
public byte[] Bytecode
{
get
{
if (_bytecode != null)
return _bytecode;
lock (this)
{
if (_bytecode != null)
return _bytecode;
var assembly = typeof(EffectResource).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(_resourceName);
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
_bytecode = memoryStream.ToArray();
}
}
return _bytecode;
}
}
}
}
@@ -0,0 +1,54 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Effects
{
/// <summary>
/// Defines an <see cref="Effect" /> that uses the standard chain of matrix transformations to represent a 3D object on
/// a 2D monitor.
/// </summary>
public interface IMatrixChainEffect
{
/// <summary>
/// Gets or sets the model-to-world <see cref="Matrix" />.
/// </summary>
/// <value>
/// The model-to-world <see cref="Matrix" />.
/// </value>
Matrix World { get; set; }
/// <summary>
/// Gets or sets the world-to-view <see cref="Matrix" />.
/// </summary>
/// <value>
/// The world-to-view <see cref="Matrix" />.
/// </value>
Matrix View { get; set; }
/// <summary>
/// Gets or sets the view-to-projection <see cref="Matrix" />.
/// </summary>
/// <value>
/// The view-to-projection <see cref="Matrix" />.
/// </value>
Matrix Projection { get; set; }
/// <summary>
/// Sets the model-to-world <see cref="Matrix" />.
/// </summary>
/// <param name="world">The model-to-world <see cref="Matrix" />.</param>
void SetWorld(ref Matrix world);
/// <summary>
/// Sets the world-to-view <see cref="Matrix" />.
/// </summary>
/// <param name="view">The world-to-view <see cref="Matrix" />.</param>
void SetView(ref Matrix view);
/// <summary>
/// Sets the view-to-projection <see cref="Matrix" />.
/// </summary>
/// <param name="projection">The view-to-projection <see cref="Matrix" />.</param>
void SetProjection(ref Matrix projection);
}
}
@@ -0,0 +1,18 @@
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Effects
{
/// <summary>
/// Defines an <see cref="Effect" /> that uses a <see cref="Texture2D" />.
/// </summary>
public interface ITexture2DEffect
{
/// <summary>
/// Gets or sets the <see cref="Texture2D" />.
/// </summary>
/// <value>
/// The <see cref="Texture2D" />.
/// </value>
Texture2D Texture { get; set; }
}
}
@@ -0,0 +1,134 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics.Effects
{
/// <summary>
/// An <see cref="Effect" /> that uses the standard chain of matrix transformations to represent a 3D object on a 2D
/// monitor.
/// </summary>
/// <seealso cref="Effect" />
/// <seealso cref="IMatrixChainEffect" />
public abstract class MatrixChainEffect : Effect, IMatrixChainEffect
{
private Matrix _projection = Matrix.Identity;
private Matrix _view = Matrix.Identity;
private Matrix _world = Matrix.Identity;
private bool _worldViewProjectionIsDirty;
private EffectParameter _worldViewProjectionParameter;
/// <summary>
/// Initializes a new instance of the <see cref="MatrixChainEffect" /> class.
/// </summary>
/// <param name="graphicsDevice">The graphics device.</param>
/// <param name="effectCode">The effect code.</param>
protected MatrixChainEffect(GraphicsDevice graphicsDevice, byte[] effectCode)
: base(graphicsDevice, effectCode)
{
CacheEffectParameters();
}
/// <summary>
/// Initializes a new instance of the <see cref="MatrixChainEffect" /> class.
/// </summary>
/// <param name="cloneSource">The clone source.</param>
protected MatrixChainEffect(Effect cloneSource)
: base(cloneSource)
{
CacheEffectParameters();
}
/// <summary>
/// Gets or sets the model-to-world <see cref="Matrix" />.
/// </summary>
/// <value>
/// The model-to-world <see cref="Matrix" />.
/// </value>
public Matrix World
{
get { return _world; }
set { SetWorld(ref value); }
}
/// <summary>
/// Gets or sets the world-to-view <see cref="Matrix" />.
/// </summary>
/// <value>
/// The world-to-view <see cref="Matrix" />.
/// </value>
public Matrix View
{
get { return _view; }
set { SetView(ref value); }
}
/// <summary>
/// Gets or sets the view-to-projection <see cref="Matrix" />.
/// </summary>
/// <value>
/// The view-to-projection <see cref="Matrix" />.
/// </value>
public Matrix Projection
{
get { return _projection; }
set { SetProjection(ref value); }
}
/// <summary>
/// Sets the model-to-world <see cref="Matrix" />.
/// </summary>
/// <param name="world">The model-to-world <see cref="Matrix" />.</param>
public void SetWorld(ref Matrix world)
{
_world = world;
_worldViewProjectionIsDirty = true;
}
/// <summary>
/// Sets the world-to-view <see cref="Matrix" />.
/// </summary>
/// <param name="view">The world-to-view <see cref="Matrix" />.</param>
public void SetView(ref Matrix view)
{
_view = view;
_worldViewProjectionIsDirty = true;
}
/// <summary>
/// Sets the view-to-projection <see cref="Matrix" />.
/// </summary>
/// <param name="projection">The view-to-projection <see cref="Matrix" />.</param>
public void SetProjection(ref Matrix projection)
{
_projection = projection;
_worldViewProjectionIsDirty = true;
}
private void CacheEffectParameters()
{
_worldViewProjectionParameter = Parameters["WorldViewProjection"];
}
/// <summary>
/// Computes derived parameter values immediately before applying the effect.
/// </summary>
protected override bool OnApply()
{
base.OnApply();
// ReSharper disable once InvertIf
if (_worldViewProjectionIsDirty)
{
_worldViewProjectionIsDirty = false;
Matrix worldViewProjection;
Matrix.Multiply(ref _world, ref _view, out worldViewProjection);
Matrix.Multiply(ref worldViewProjection, ref _projection, out worldViewProjection);
_worldViewProjectionParameter.SetValue(worldViewProjection);
}
// in MonoGame 3.6 this method won't return anything; see https://github.com/MonoGame/MonoGame/pull/5090
return false;
}
}
}
@@ -0,0 +1,56 @@
// The matrix that transforms geometry in the vertex shader
float4x4 WorldViewProjection;
// The texture used for texture sampling in the pixel shader
Texture2D<float4> Texture;
// The sampler used for texture sampling in the pixel shader
SamplerState TextureSampler;
// The information layout used as input to the vertex shader. The actual information is passed to the GPU from the CPU
struct VertexShaderInput
{
float4 Position : POSITION0;
float4 Color : COLOR0;
float2 TexureCoordinate : TEXCOORD0;
};
// The information layout used as output from the vertex shader and input to the pixel shader
struct VertexShaderOutput
{
float4 Position : SV_Position;
float4 Color : COLOR0;
float2 TexureCoordinate : TEXCOORD0;
};
// The information layout used as output from the pixel shader. The actual information is what is displayed on screen
struct PixelShaderOutput
{
float4 Color : COLOR0;
};
// The vertex shader code that transforms render geometry. The input information is from the CPU
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
output.Position = mul(input.Position, WorldViewProjection);
output.Color = input.Color;
output.TexureCoordinate = input.TexureCoordinate;
return output;
}
// The pixel shader code that determines the final color of each pixel. The input information is the output of the vertex shader
PixelShaderOutput PixelShaderFunction(VertexShaderOutput input) : SV_Target0
{
PixelShaderOutput output;
output.Color = Texture.Sample(TextureSampler, input.TexureCoordinate) * input.Color;
return output;
}
// The technique; a collection of passes to be applied, each specifying the vertex and shader to use
technique
{
pass
{
VertexShader = compile vs_2_0 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
@@ -0,0 +1,6 @@
:: Can only use 2MGFX.exe on Windows...
SET PATH=%PATH%;C:\Program Files (x86)\MSBuild\MonoGame\v3.0\Tools
2MGFX.exe %cd%\DefaultEffect2D.fx %cd%\DefaultEffect2D.mgfxo
pause
@@ -0,0 +1,37 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
public class DynamicGeometryBuffer<TVertexType> : GeometryBuffer<TVertexType>
where TVertexType : struct, IVertexType
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamicGeometryBuffer{TVertexType}" /> class.
/// </summary>
/// <param name="graphicsDevice">The graphics device.</param>
/// <param name="maximumVerticesCount">The maximum number of vertices.</param>
/// <param name="maximumIndicesCount">The maximum number of indices.</param>
/// <exception cref="ArgumentNullException"><paramref name="graphicsDevice" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="maximumVerticesCount" /> is <code>0</code>, or, <paramref name="maximumVerticesCount" /> is
/// <code>0</code>.
/// </exception>
/// <remarks>
/// <para>
/// For best performance, use <see cref="DynamicGeometryBuffer{TVertexType}" /> for geometry which changes
/// frame-to-frame and <see cref="StaticGeometryBuffer{TVertexType}" /> for geoemtry which does not change
/// frame-to-frame, or changes infrequently between frames.
/// </para>
/// <para>
/// Memory will be allocated for the vertex and index array buffers in proportion to
/// <paramref name="maximumVerticesCount" /> and <paramref name="maximumIndicesCount" /> respectively.
/// </para>
/// </remarks>
public DynamicGeometryBuffer(GraphicsDevice graphicsDevice, ushort maximumVerticesCount,
ushort maximumIndicesCount)
: base(graphicsDevice, GeometryBufferType.Dynamic, maximumVerticesCount, maximumIndicesCount)
{
}
}
}
@@ -0,0 +1,37 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
public class StaticGeometryBuffer<TVertexType> : GeometryBuffer<TVertexType>
where TVertexType : struct, IVertexType
{
/// <summary>
/// Initializes a new instance of the <see cref="GeometryBuffer{TVertexType}" /> class.
/// </summary>
/// <param name="graphicsDevice">The graphics device.</param>
/// <param name="maximumVerticesCount">The maximum number of vertices.</param>
/// <param name="maximumIndicesCount">The maximum number of indices.</param>
/// <exception cref="ArgumentNullException"><paramref name="graphicsDevice" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="maximumVerticesCount" /> is <code>0</code>, or, <paramref name="maximumVerticesCount" /> is
/// <code>0</code>.
/// </exception>
/// <remarks>
/// <para>
/// For best performance, use <see cref="DynamicGeometryBuffer{TVertexType}" /> for geometry which changes
/// frame-to-frame and <see cref="StaticGeometryBuffer{TVertexType}" /> for geoemtry which does not change
/// frame-to-frame, or changes infrequently between frames.
/// </para>
/// <para>
/// Memory will be allocated for the vertex and index array buffers in proportion to
/// <paramref name="maximumVerticesCount" /> and <paramref name="maximumIndicesCount" /> respectively.
/// </para>
/// </remarks>
public StaticGeometryBuffer(GraphicsDevice graphicsDevice, ushort maximumVerticesCount,
ushort maximumIndicesCount)
: base(graphicsDevice, GeometryBufferType.Static, maximumVerticesCount, maximumIndicesCount)
{
}
}
}
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
internal enum GeometryBufferType
{
Static,
Dynamic
}
/// <summary>
/// Stores geometry which is to be uploaded to a <see cref="GraphicsDevice" />.
/// </summary>
/// <typeparam name="TVertexType">The type of the vertex type.</typeparam>
/// <seealso cref="IDisposable" />
/// <remarks>
/// <para>
/// For best performance, use <see cref="DynamicGeometryBuffer{TVertexType}" /> for geometry which changes
/// frame-to-frame and <see cref="StaticGeometryBuffer{TVertexType}" /> for geoemtry which does not change
/// frame-to-frame, or changes infrequently between frames.
/// </para>
/// </remarks>
// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
public abstract class GeometryBuffer<TVertexType> : IDisposable
where TVertexType : struct, IVertexType
{
internal readonly GeometryBufferType BufferType;
internal GeometryBuffer(GraphicsDevice graphicsDevice, GeometryBufferType bufferType,
ushort maximumVerticesCount, ushort maximumIndicesCount)
{
if (graphicsDevice == null)
throw new ArgumentNullException(nameof(graphicsDevice));
if (maximumVerticesCount <= 0)
throw new ArgumentOutOfRangeException(nameof(maximumVerticesCount));
if (maximumIndicesCount <= 0)
throw new ArgumentOutOfRangeException(nameof(maximumIndicesCount));
if ((bufferType != GeometryBufferType.Static) && (bufferType != GeometryBufferType.Dynamic))
throw new ArgumentOutOfRangeException(nameof(bufferType));
GraphicsDevice = graphicsDevice;
_vertices = new TVertexType[maximumVerticesCount];
_indices = new ushort[maximumIndicesCount];
_vertexCount = 0;
MaximumVerticesCount = maximumVerticesCount;
MaximumIndicesCount = maximumIndicesCount;
BufferType = bufferType;
switch (bufferType)
{
case GeometryBufferType.Static:
VertexBuffer = new VertexBuffer(graphicsDevice, typeof(TVertexType), maximumVerticesCount,
BufferUsage.WriteOnly);
IndexBuffer = new IndexBuffer(graphicsDevice, typeof(ushort), maximumIndicesCount,
BufferUsage.WriteOnly);
break;
case GeometryBufferType.Dynamic:
VertexBuffer = new DynamicVertexBuffer(graphicsDevice, typeof(TVertexType), maximumVerticesCount,
BufferUsage.WriteOnly);
IndexBuffer = new DynamicIndexBuffer(graphicsDevice, typeof(ushort), maximumIndicesCount,
BufferUsage.WriteOnly);
break;
default:
throw new ArgumentOutOfRangeException(nameof(bufferType));
}
}
/// <summary>
/// Gets the vertices as read-only.
/// </summary>
/// <value>
/// The vertices as read-only.
/// </value>
public IReadOnlyList<TVertexType> Vertices => _vertices;
/// <summary>
/// Gets the indices as read-only.
/// </summary>
/// <value>
/// The indices as read-only.
/// </value>
public IReadOnlyList<ushort> Indices => _indices;
/// <summary>
/// Gets the number of buffered vertices.
/// </summary>
/// <value>
/// The number of buffered vertices.
/// </value>
public ushort VertexCount => _vertexCount;
/// <summary>
/// Gets the number of buffered indices.
/// </summary>
/// <value>
/// The number of buffered indices.
/// </value>
public ushort IndexCount => _indexCount;
public ushort MaximumVerticesCount { get; }
public ushort MaximumIndicesCount { get; }
/// <summary>
/// Gets the vertex buffer.
/// </summary>
/// <value>
/// The vertex buffer.
/// </value>
public VertexBuffer VertexBuffer { get; }
/// <summary>
/// Gets the index buffer.
/// </summary>
/// <value>
/// The index buffer.
/// </value>
public IndexBuffer IndexBuffer { get; }
/// <summary>
/// Gets the <see cref="GraphicsDevice" /> associated with this
/// <see cref="GeometryBuffer{TVertexType}" />.
/// </summary>
/// <value>
/// The <see cref="GraphicsDevice" /> associated with this
/// <see cref="GeometryBuffer{TVertexType}" />.
/// </value>
public GraphicsDevice GraphicsDevice { get; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Uploads the buffered geometry to the <see cref="GraphicsDevice" />.
/// </summary>
public void Flush()
{
VertexBuffer.SetData(_vertices, 0, _vertexCount);
IndexBuffer.SetData(_indices, 0, _indexCount);
}
/// <summary>
/// Adds the specified vertex to the buffer of geometry.
/// </summary>
/// <param name="vertex">The vertex.</param>
public bool Enqueue(ref TVertexType vertex)
{
ThrowIfWouldOverflowVertices(1);
_vertices[_vertexCount++] = vertex;
return true;
}
/// <summary>
/// Adds the specified vertex index to the buffer of geometry.
/// </summary>
/// <param name="index">The vertex index.</param>
/// <exception cref="GeometryBufferOverflowException{TVertexType}">The <see cref="GeometryBuffer{TVertexType}" /> is full.</exception>
public void Enqueue(ushort index)
{
ThrowIfWouldOverflowIndices(1);
_indices[_indexCount++] = index;
}
/// <summary>
/// Attempts to add the specified vertices to the buffer of geometry.
/// </summary>
/// <param name="vertices">The vertices.</param>
/// <param name="sourceIndex">
/// The value that represents the index in the <paramref name="vertices" /> at which adding
/// begins.
/// </param>
/// <param name="length">The number of vertices to add.</param>
/// <exception cref="GeometryBufferOverflowException{TVertexType}">The <see cref="GeometryBuffer{TVertexType}" /> is full.</exception>
public void Enqueue(TVertexType[] vertices, int sourceIndex, ushort length)
{
ThrowIfWouldOverflowVertices(length);
Array.Copy(vertices, sourceIndex, _vertices, VertexCount, length);
_vertexCount += length;
}
/// <summary>
/// Adds vertex indices to the buffer of geometry.
/// </summary>
/// <param name="indices">The indices.</param>
/// <param name="sourceIndex">
/// The value that represents the index in the <paramref name="indices" /> at which adding
/// begins.
/// </param>
/// <param name="length">The number of indices to add.</param>
/// <param name="indexOffset">The index offset.</param>
/// <exception cref="GeometryBufferOverflowException{TVertexType}">The <see cref="GeometryBuffer{TVertexType}" /> is full.</exception>
public unsafe void Enqueue(ushort[] indices, int sourceIndex, ushort length, ushort indexOffset)
{
ThrowIfWouldOverflowIndices(length);
var start = _indexCount;
var end = _indexCount + length;
Array.Copy(indices, sourceIndex, _indices, IndexCount, length);
fixed (ushort* fixedPointer = _indices)
{
var pointer = fixedPointer + start;
var endPointer = fixedPointer + end;
while (pointer < endPointer)
{
*pointer += indexOffset;
++pointer;
}
}
_indexCount += length;
}
/// <summary>
/// Adds clockwise quadrilateral indices to the buffer of geometry.
/// </summary>
/// <param name="indexOffset">The index offset.</param>
/// <exception cref="GeometryBufferOverflowException{TVertexType}">The <see cref="GeometryBuffer{TVertexType}" /> is full.</exception>
public unsafe void EnqueueClockwiseQuadrilateralIndices(ushort indexOffset)
{
ThrowIfWouldOverflowIndices(6);
fixed (ushort* fixedPointer = _indices)
{
var pointer = fixedPointer + _indexCount;
*(pointer + 0) = (ushort) (0 + indexOffset);
*(pointer + 1) = (ushort) (1 + indexOffset);
*(pointer + 2) = (ushort) (2 + indexOffset);
*(pointer + 3) = (ushort) (1 + indexOffset);
*(pointer + 4) = (ushort) (3 + indexOffset);
*(pointer + 5) = (ushort) (2 + indexOffset);
}
_indexCount += 6;
}
/// <summary>
/// Resets the buffered geometry count.
/// </summary>
public void Clear()
{
_vertexCount = 0;
_indexCount = 0;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing">
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only
/// unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (!disposing)
return;
VertexBuffer.Dispose();
IndexBuffer.Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ThrowIfWouldOverflowVertices(ushort verticesCountToAdd)
{
if (_vertexCount + verticesCountToAdd > MaximumVerticesCount)
throw new GeometryBufferOverflowException<TVertexType>(this, verticesCountToAdd, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ThrowIfWouldOverflowIndices(ushort indicesCountToAdd)
{
if (_indexCount + indicesCountToAdd > MaximumIndicesCount)
throw new GeometryBufferOverflowException<TVertexType>(this, 0, indicesCountToAdd);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ThrowIfWouldOverflow(ushort verticesCountToAdd, ushort indicesCountToAdd)
{
if ((_vertexCount + verticesCountToAdd > MaximumVerticesCount) ||
(_indexCount + indicesCountToAdd > MaximumIndicesCount))
throw new GeometryBufferOverflowException<TVertexType>(this, verticesCountToAdd, indicesCountToAdd);
}
//TODO: Add TIndexType to allow for different type of indices: int, short, etc
// ReSharper disable InconsistentNaming
internal readonly TVertexType[] _vertices;
internal readonly ushort[] _indices;
internal ushort _vertexCount;
internal ushort _indexCount;
// ReSharper restore InconsistentNaming
}
}
@@ -0,0 +1,149 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
public static class GeometryBuilderExtensionsVertexPositionColorTexture
{
/// <summary>
/// Adds a sprite to the back of specified <see cref="GeometryBuffer{VertexPositionColorTexture}" />.
/// </summary>
/// <param name="geometryBuffer">The <see cref="GeometryBuffer{VertexPositionColorTexture}" />.</param>
/// <param name="indexOffset">The vertex index offset applied to generated indices.</param>
/// <param name="texture">The <see cref="Texture" />.</param>
/// <param name="transformMatrix">The transform <see cref="Matrix2D" />.</param>
/// <param name="sourceRectangle">
/// The texture region <see cref="Rectangle" /> of the <paramref name="texture" />. Use
/// <code>null</code> to use the entire <see cref="Texture2D" />.
/// </param>
/// <param name="size">
/// The <see cref="Size" /> of the sprite. Use <code>null</code> to use the default size which is either
/// the size of the <paramref name="sourceRectangle" /> if it is not <code>null</code>, or the texture size if
/// <paramref name="sourceRectangle" /> is <code>null</code>.
/// </param>
/// <param name="color">The <see cref="Color" />. Use <code>null</code> to use the default <see cref="Color.White" />.</param>
/// <param name="origin">
/// The origin <see cref="Vector2" />. Use <code>null</code> to use the default
/// <see cref="Vector2.Zero" />.
/// </param>
/// <param name="effects">The <see cref="SpriteEffects" />. The default value is <see cref="SpriteEffects.None" />.</param>
/// <param name="depth">The depth. The default value is <code>0</code>.</param>
/// <exception cref="ArgumentNullException"><paramref name="texture" /> is null.</exception>
/// <exception cref="GeometryBufferOverflowException{TVertexType}">
/// The underlying
/// <see cref="GeometryBuffer{TVertexType}" /> is full.
/// </exception>
public static unsafe void EnqueueSprite(this GeometryBuffer<VertexPositionColorTexture> geometryBuffer,
ushort indexOffset, Texture2D texture, ref Matrix2D transformMatrix, Rectangle? sourceRectangle = null,
Size? size = null,
Color? color = null, Vector2? origin = null, SpriteEffects effects = SpriteEffects.None, float depth = 0)
{
if (texture == null)
throw new ArgumentNullException(nameof(texture));
geometryBuffer.ThrowIfWouldOverflow(4, 6);
var origin1 = origin ?? Vector2.Zero;
Vector2 positionTopLeft, positionBottomRight, textureCoordinateTopLeft, textureCoordinateBottomRight;
Size size1;
if (sourceRectangle == null)
{
size1 = size ?? new Size(texture.Width, texture.Height);
textureCoordinateTopLeft.X = 0;
textureCoordinateTopLeft.Y = 0;
textureCoordinateBottomRight.X = 1;
textureCoordinateBottomRight.Y = 1;
}
else
{
var textureRegion = sourceRectangle.Value;
size1 = size ?? new Size(textureRegion.Width, textureRegion.Height);
textureCoordinateTopLeft.X = textureRegion.X/(float) texture.Width;
textureCoordinateTopLeft.Y = textureRegion.Y/(float) texture.Height;
textureCoordinateBottomRight.X = (textureRegion.X + textureRegion.Width)/(float) texture.Width;
textureCoordinateBottomRight.Y = (textureRegion.Y + textureRegion.Height)/(float) texture.Height;
}
positionTopLeft.X = -origin1.X;
positionTopLeft.Y = -origin1.Y;
positionBottomRight.X = -origin1.X + size1.Width;
positionBottomRight.Y = -origin1.Y + size1.Height;
var spriteEffect = effects;
if ((spriteEffect & SpriteEffects.FlipVertically) != 0)
{
var temp = textureCoordinateBottomRight.Y;
textureCoordinateBottomRight.Y = textureCoordinateTopLeft.Y;
textureCoordinateTopLeft.Y = temp;
}
if ((spriteEffect & SpriteEffects.FlipHorizontally) != 0)
{
var temp = textureCoordinateBottomRight.X;
textureCoordinateBottomRight.X = textureCoordinateTopLeft.X;
textureCoordinateTopLeft.X = temp;
}
var vertex = new VertexPositionColorTexture(new Vector3(Vector2.Zero, depth), color ?? Color.White,
Vector2.Zero);
fixed (VertexPositionColorTexture* fixedPointer = geometryBuffer._vertices)
{
var pointer = fixedPointer + geometryBuffer._vertexCount;
// top-left
var position = positionTopLeft;
transformMatrix.Transform(position, out position);
vertex.Position.X = position.X;
vertex.Position.Y = position.Y;
vertex.TextureCoordinate = textureCoordinateTopLeft;
*(pointer + 0) = vertex;
// top-right
position.X = positionBottomRight.X;
position.Y = positionTopLeft.Y;
transformMatrix.Transform(position, out position);
vertex.Position.X = position.X;
vertex.Position.Y = position.Y;
vertex.TextureCoordinate.X = textureCoordinateBottomRight.X;
vertex.TextureCoordinate.Y = textureCoordinateTopLeft.Y;
*(pointer + 1) = vertex;
// bottom-left
position.X = positionTopLeft.X;
position.Y = positionBottomRight.Y;
transformMatrix.Transform(position, out position);
vertex.Position.X = position.X;
vertex.Position.Y = position.Y;
vertex.TextureCoordinate.X = textureCoordinateTopLeft.X;
vertex.TextureCoordinate.Y = textureCoordinateBottomRight.Y;
*(pointer + 2) = vertex;
// bottom-right
position = positionBottomRight;
transformMatrix.Transform(position, out position);
vertex.Position.X = position.X;
vertex.Position.Y = position.Y;
vertex.TextureCoordinate = textureCoordinateBottomRight;
*(pointer + 3) = vertex;
}
geometryBuffer._vertexCount += 4;
fixed (ushort* fixedPointer = geometryBuffer._indices)
{
var pointer = fixedPointer + geometryBuffer._indexCount;
*(pointer + 0) = (ushort) (0 + indexOffset);
*(pointer + 1) = (ushort) (1 + indexOffset);
*(pointer + 2) = (ushort) (2 + indexOffset);
*(pointer + 3) = (ushort) (1 + indexOffset);
*(pointer + 4) = (ushort) (3 + indexOffset);
*(pointer + 5) = (ushort) (2 + indexOffset);
}
geometryBuffer._indexCount += 6;
}
}
}
@@ -0,0 +1,23 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
/// <summary>
/// The exception that is thrown when enqueuing vertices or indices into a <see cref="GeometryBuffer{TVertexType}" />
/// results in an overflow.
/// </summary>
/// <typeparam name="TVertexType">The vertex type.</typeparam>
/// <seealso cref="Exception" />
public class GeometryBufferOverflowException<TVertexType> : Exception
where TVertexType : struct, IVertexType
{
internal GeometryBufferOverflowException(GeometryBuffer<TVertexType> geometryBuffer, int verticesCountToAdd,
int indicesCountToAdd)
: base(
$"The GeometryBuffer could not enqueue the requested {verticesCountToAdd} vertices or {indicesCountToAdd} indices since it is full full with {geometryBuffer._vertexCount} (out of {geometryBuffer.MaximumVerticesCount}) vertices and {geometryBuffer._indexCount} (out of {geometryBuffer.MaximumIndicesCount}) indices. Consider increasing the maximum number of vertices or indices when instantiating the GeometryBuffer, flushing the GeometryBuffer if it's dynamic, or using multiple GeometryBuffers to accommodate the requested enqueue."
)
{
}
}
}
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9B3AB8A1-78AA-471A-AFD0-B10B932115BC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MonoGame.Extended.Graphics</RootNamespace>
<AssemblyName>MonoGame.Extended.Graphics</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="Effects\Resources\compile.bat" />
<EmbeddedResource Include="Effects\Resources\DefaultEffect2D.mgfxo" />
</ItemGroup>
<ItemGroup>
<Compile Include="Batch2DSortMode.cs" />
<Compile Include="Batching\Batch.cs" />
<Compile Include="Batching\BatchCommandDrawer.cs" />
<Compile Include="Batching\BatchCommandQueue.cs" />
<Compile Include="Batching\BatchCommandQueueOverflowException.cs" />
<Compile Include="Batching\BatchDrawCommand.cs" />
<Compile Include="Batching\BatchSortMode.cs" />
<Compile Include="Batching\DeferredBatchCommandQueue.cs" />
<Compile Include="Batching\IBatchDrawCommandData.cs" />
<Compile Include="Batching\ImmediateBatchCommandQueue.cs" />
<Compile Include="DynamicBatch2D.cs" />
<Compile Include="Effects\DefaultEffect2D.cs" />
<Compile Include="Effects\EffectResource.cs" />
<Compile Include="Effects\IMatrixChainEffect.cs" />
<Compile Include="Effects\ITexture2DEffect.cs" />
<Compile Include="Effects\MatrixChainEffect.cs" />
<Compile Include="GeometryBuffer.cs" />
<Compile Include="GeometryBuffer.Dynamic.cs" />
<Compile Include="GeometryBuffer.Static.cs" />
<Compile Include="GeometryBufferExtensions.VertexPositionColorTexture.cs" />
<Compile Include="GeometryBufferOverflowException.cs" />
<Compile Include="PrimitiveTypeExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Effects\Resources\DefaultEffect2D.fx" />
</ItemGroup>
<ItemGroup>
<Reference Include="MonoGame.Framework">
<HintPath>..\..\Dependencies\MonoGame.Framework.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj">
<Project>{41724c52-3d50-45bb-81eb-3c8a247eafd1}</Project>
<Name>MonoGame.Extended</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -0,0 +1,42 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics
{
public static class PrimitiveTypeExtensions
{
internal static int GetPrimitivesCount(this PrimitiveType primitiveType, int verticesCount)
{
switch (primitiveType)
{
case PrimitiveType.LineStrip:
return verticesCount - 1;
case PrimitiveType.LineList:
return verticesCount/2;
case PrimitiveType.TriangleStrip:
return verticesCount - 2;
case PrimitiveType.TriangleList:
return verticesCount/3;
default:
throw new ArgumentException("Invalid primitive type.");
}
}
internal static int GetVerticesCount(this PrimitiveType primitiveType, int primitivesCount)
{
switch (primitiveType)
{
case PrimitiveType.LineStrip:
return primitivesCount + 1;
case PrimitiveType.LineList:
return primitivesCount*2;
case PrimitiveType.TriangleStrip:
return primitivesCount + 2;
case PrimitiveType.TriangleList:
return primitivesCount*3;
default:
throw new ArgumentException("Invalid primitive type.");
}
}
}
}
@@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MonoGame.Extended.Graphics")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MonoGame.Extended.Graphics")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]