Initial work on improving rendering; separated threading from mesh generation

This commit is contained in:
William Moorehouse 2015-06-17 19:23:24 -04:00
parent f5f0958bf1
commit 341093384d
7 changed files with 338 additions and 159 deletions

View File

@ -30,6 +30,23 @@ namespace TrueCraft.Client.Rendering
SetSubmesh(0, indices);
}
/// <summary>
///
/// </summary>
/// <param name="chunk"></param>
/// <param name="device"></param>
/// <param name="vertices"></param>
/// <param name="opaqueIndices"></param>
/// <param name="transparentIndices"></param>
public ChunkMesh(ReadOnlyChunk chunk, GraphicsDevice device, VertexPositionNormalTexture[] vertices, int[] opaqueIndices, int[] transparentIndices)
: base(device, 2, true)
{
Chunk = chunk;
Vertices = vertices;
SetSubmesh(0, opaqueIndices);
SetSubmesh(1, transparentIndices);
}
/// <summary>
///
/// </summary>

View File

@ -19,7 +19,7 @@ namespace TrueCraft.Client.Rendering
/// A daemon of sorts that creates meshes from chunks.
/// Passing meshes back is NOT thread-safe.
/// </summary>
public class ChunkRenderer
public class ChunkRenderer : Renderer<ReadOnlyChunk>
{
public class ChunkSorter : Comparer<Mesh>
{
@ -39,85 +39,16 @@ namespace TrueCraft.Client.Rendering
}
}
public class MeshGeneratedEventArgs : EventArgs
{
public bool Transparent { get; set; }
public ChunkMesh Mesh { get; set; }
public MeshGeneratedEventArgs(ChunkMesh mesh, bool transparent)
{
Transparent = transparent;
Mesh = mesh;
}
}
public event EventHandler<MeshGeneratedEventArgs> MeshGenerated;
private ConcurrentQueue<ReadOnlyChunk> ChunkQueue { get; set; }
private GraphicsDevice GraphicsDevice { get; set; }
private IBlockRepository BlockRepository { get; set; }
private List<Tuple<Thread, CancellationTokenSource>> RenderThreads { get; set; }
public ChunkRenderer(GraphicsDevice graphics, IBlockRepository blockRepository)
: base()
{
ChunkQueue = new ConcurrentQueue<ReadOnlyChunk>();
RenderThreads = new List<Tuple<Thread, CancellationTokenSource>>();
var threads = Environment.ProcessorCount - 2;
if (threads < 1)
threads = 1;
for (int i = 0; i < threads; i++)
{
var token = new CancellationTokenSource();
var worker = new Thread(() => DoChunks(token));
worker.IsBackground = true;
RenderThreads.Add(new Tuple<Thread, CancellationTokenSource>(worker, token));
}
BlockRepository = blockRepository;
GraphicsDevice = graphics;
}
public void QueueChunk(ReadOnlyChunk chunk)
{
ChunkQueue.Enqueue(chunk);
}
public void QueueHighPriorityChunk(ReadOnlyChunk chunk)
{
// TODO: Overwrite existing chunks
Task.Factory.StartNew(() =>
{
ProcessChunk(chunk, new RenderState());
});
}
public void Start()
{
RenderThreads.ForEach(t => t.Item1.Start());
}
public void Stop()
{
RenderThreads.ForEach(t => t.Item2.Cancel());
}
private void DoChunks(CancellationTokenSource token)
{
var state = new RenderState();
while (true)
{
if (token.Token.IsCancellationRequested)
return;
ReadOnlyChunk chunk;
if (ChunkQueue.TryDequeue(out chunk))
{
ProcessChunk(chunk, state);
}
Thread.Sleep(1);
}
}
private static readonly Coordinates3D[] AdjacentCoordinates =
{
Coordinates3D.Up,
@ -128,20 +59,30 @@ namespace TrueCraft.Client.Rendering
Coordinates3D.West
};
protected override bool TryRender(ReadOnlyChunk item, out Mesh result)
{
var state = new RenderState();
ProcessChunk(item, state);
result = new ChunkMesh(item, GraphicsDevice, state.Verticies.ToArray(),
state.OpaqueIndicies.ToArray(), state.TransparentIndicies.ToArray());
return (result != null);
}
private class RenderState
{
public readonly List<VertexPositionNormalTexture> OpaqueVerticies = new List<VertexPositionNormalTexture>();
public readonly List<VertexPositionNormalTexture> Verticies = new List<VertexPositionNormalTexture>();
public readonly List<int> OpaqueIndicies = new List<int>();
public readonly List<VertexPositionNormalTexture> TransparentVerticies = new List<VertexPositionNormalTexture>();
public readonly List<int> TransparentIndicies = new List<int>();
public readonly HashSet<Coordinates3D> DrawableCoordinates = new HashSet<Coordinates3D>();
}
private void ProcessChunk(ReadOnlyChunk chunk, RenderState state)
{
state.OpaqueVerticies.Clear();
state.Verticies.Clear();
state.OpaqueIndicies.Clear();
state.TransparentVerticies.Clear();
state.TransparentIndicies.Clear();
state.DrawableCoordinates.Clear();
@ -198,8 +139,8 @@ namespace TrueCraft.Client.Rendering
int[] i;
var v = BlockRenderer.RenderBlock(provider, descriptor,
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
state.OpaqueVerticies.Count, out i);
state.OpaqueVerticies.AddRange(v);
state.Verticies.Count, out i);
state.Verticies.AddRange(v);
state.OpaqueIndicies.AddRange(i);
}
else
@ -207,18 +148,11 @@ namespace TrueCraft.Client.Rendering
int[] i;
var v = BlockRenderer.RenderBlock(provider, descriptor,
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
state.TransparentVerticies.Count, out i);
state.TransparentVerticies.AddRange(v);
state.Verticies.Count, out i);
state.Verticies.AddRange(v);
state.TransparentIndicies.AddRange(i);
}
}
var opaque = new ChunkMesh(chunk, GraphicsDevice, state.OpaqueVerticies.ToArray(), state.OpaqueIndicies.ToArray());
var transparent = new ChunkMesh(chunk, GraphicsDevice, state.TransparentVerticies.ToArray(), state.TransparentIndicies.ToArray());
if (MeshGenerated != null)
{
MeshGenerated(this, new MeshGeneratedEventArgs(opaque, false));
MeshGenerated(this, new MeshGeneratedEventArgs(transparent, true));
}
}
}
}

View File

@ -0,0 +1,193 @@
using System;
using System.Threading;
using System.Collections.Concurrent;
namespace TrueCraft.Client.Rendering
{
/// <summary>
/// Abstract base class for renderers of meshes.
/// </summary>
/// <typeparam name="T">The object to render into a mesh.</typeparam>
public abstract class Renderer<T> : IDisposable
{
private readonly object _syncLock =
new object();
/// <summary>
///
/// </summary>
public event EventHandler<RendererEventArgs<T>> MeshCompleted;
private volatile bool _isRunning;
private Thread _rendererThread;
private ConcurrentQueue<T> _items, _priorityItems;
private volatile bool _isDisposed;
/// <summary>
/// Gets whether this renderer is running.
/// </summary>
public bool IsRunning
{
get
{
if (_isDisposed)
throw new ObjectDisposedException(GetType().Name);
return _isRunning;
}
}
/// <summary>
/// Gets whether this renderer is disposed of.
/// </summary>
public bool IsDisposed
{
get { return _isDisposed; }
}
/// <summary>
///
/// </summary>
protected Renderer()
{
lock (_syncLock)
{
_isRunning = false;
_rendererThread = new Thread(DoRendering);
_items = new ConcurrentQueue<T>(); _priorityItems = new ConcurrentQueue<T>();
_isDisposed = false;
}
}
/// <summary>
/// Starts this renderer.
/// </summary>
public void Start()
{
if (_isDisposed)
throw new ObjectDisposedException(GetType().Name);
if (_isRunning) return;
lock (_syncLock)
{
_isRunning = true;
_rendererThread.Start(null);
}
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
private void DoRendering(object obj)
{
while (_isRunning)
{
var item = default(T);
var result = default(Mesh);
lock (_syncLock)
{
if (_priorityItems.TryDequeue(out item) && TryRender(item, out result))
{
var args = new RendererEventArgs<T>(item, result, true);
if (MeshCompleted != null)
MeshCompleted(this, args);
}
else if (_items.TryDequeue(out item) && TryRender(item, out result))
{
var args = new RendererEventArgs<T>(item, result, false);
if (MeshCompleted != null)
MeshCompleted(this, args);
}
}
if (item == null) // We don't have any work, so sleep for a bit.
Thread.Sleep(100);
}
}
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <param name="result"></param>
/// <returns></returns>
protected abstract bool TryRender(T item, out Mesh result);
/// <summary>
/// Stops this renderer.
/// </summary>
public void Stop()
{
if (_isDisposed)
throw new ObjectDisposedException(GetType().Name);
if (!_isRunning) return;
lock (_syncLock)
{
_isRunning = false;
_rendererThread.Join();
}
}
/// <summary>
/// Enqueues an item to this renderer for rendering.
/// </summary>
/// <param name="item"></param>
/// <param name="hasPriority"></param>
public void Enqueue(T item, bool hasPriority = false)
{
if (_isDisposed)
throw new ObjectDisposedException(GetType().Name);
if (!_isRunning) return;
lock (_syncLock)
{
if (hasPriority)
_priorityItems.Enqueue(item);
else
_items.Enqueue(item);
}
}
/// <summary>
/// Disposes of this renderer.
/// </summary>
public void Dispose()
{
if (_isDisposed)
return;
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes of this renderer.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Stop();
lock (_syncLock)
{
_rendererThread = null;
_items = null; _priorityItems = null;
_isDisposed = true;
}
}
else
throw new NotSupportedException(); // We should 'encourage' developers to dispose of renderers properly.
}
/// <summary>
/// Finalizes this renderer.
/// </summary>
~Renderer()
{
Dispose(false);
}
}
}

View File

@ -0,0 +1,39 @@
using System;
namespace TrueCraft.Client.Rendering
{
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class RendererEventArgs<T> : EventArgs
{
/// <summary>
///
/// </summary>
public T Item { get; private set; }
/// <summary>
///
/// </summary>
public Mesh Result { get; private set; }
/// <summary>
///
/// </summary>
public bool IsPriority { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <param name="result"></param>
/// <param name="isPriority"></param>
public RendererEventArgs(T item, Mesh result, bool isPriority)
{
Item = item;
Result = result;
IsPriority = isPriority;
}
}
}

View File

@ -105,6 +105,8 @@
<Compile Include="Rendering\Font.cs" />
<Compile Include="Rendering\FontRenderer.cs" />
<Compile Include="Rendering\FontStyle.cs" />
<Compile Include="Rendering\Renderer.cs" />
<Compile Include="Rendering\RendererEventArgs.cs" />
<Compile Include="Rendering\TextureMapper.cs" />
<Compile Include="TrueCraftGame.cs" />
<Compile Include="MultiplayerClient.cs" />

View File

@ -28,11 +28,9 @@ namespace TrueCraft.Client
private IPEndPoint EndPoint { get; set; }
private ChunkRenderer ChunkConverter { get; set; }
private DateTime NextPhysicsUpdate { get; set; }
private List<ChunkMesh> ChunkMeshes { get; set; }
private List<Mesh> ChunkMeshes { get; set; }
private ConcurrentBag<Action> PendingMainThreadActions { get; set; }
private ConcurrentBag<ChunkMesh> IncomingChunks { get; set; }
private ConcurrentBag<ChunkMesh> IncomingTransparentChunks { get; set; }
private List<ChunkMesh> TransparentChunkMeshes { get; set; }
private ConcurrentBag<Mesh> IncomingChunks { get; set; }
public ChatInterface ChatInterface { get; set; }
private RenderTarget2D RenderTarget;
private BoundingFrustum CameraView;
@ -51,16 +49,15 @@ namespace TrueCraft.Client
Window.Title = "TrueCraft";
Content.RootDirectory = "Content";
Graphics = new GraphicsDeviceManager(this);
Graphics.SynchronizeWithVerticalRetrace = false;
Graphics.IsFullScreen = UserSettings.Local.IsFullscreen;
Graphics.PreferredBackBufferWidth = UserSettings.Local.WindowResolution.Width;
Graphics.PreferredBackBufferHeight = UserSettings.Local.WindowResolution.Height;
Client = client;
EndPoint = endPoint;
NextPhysicsUpdate = DateTime.MinValue;
ChunkMeshes = new List<ChunkMesh>();
TransparentChunkMeshes = new List<ChunkMesh>();
IncomingChunks = new ConcurrentBag<ChunkMesh>();
IncomingTransparentChunks = new ConcurrentBag<ChunkMesh>();
ChunkMeshes = new List<Mesh>();
IncomingChunks = new ConcurrentBag<Mesh>();
PendingMainThreadActions = new ConcurrentBag<Action>();
MouseCaptured = true;
@ -79,9 +76,9 @@ namespace TrueCraft.Client
SpriteBatch = new SpriteBatch(GraphicsDevice);
base.Initialize(); // (calls LoadContent)
ChunkConverter = new ChunkRenderer(Graphics.GraphicsDevice, Client.World.World.BlockRepository);
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
Client.ChunkModified += (sender, e) => ChunkConverter.QueueHighPriorityChunk(e.Chunk);
ChunkConverter.MeshGenerated += ChunkConverter_MeshGenerated;
Client.ChunkLoaded += (sender, e) => ChunkConverter.Enqueue(e.Chunk);
Client.ChunkModified += (sender, e) => ChunkConverter.Enqueue(e.Chunk, true);
ChunkConverter.MeshCompleted += ChunkConverter_MeshGenerated;
ChunkConverter.Start();
Client.PropertyChanged += HandleClientPropertyChanged;
Client.Connect(EndPoint);
@ -103,12 +100,9 @@ namespace TrueCraft.Client
false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
}
void ChunkConverter_MeshGenerated(object sender, ChunkRenderer.MeshGeneratedEventArgs e)
void ChunkConverter_MeshGenerated(object sender, RendererEventArgs<ReadOnlyChunk> e)
{
if (e.Transparent)
IncomingTransparentChunks.Add(e.Mesh);
else
IncomingChunks.Add(e.Mesh);
IncomingChunks.Add(e.Result);
}
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
@ -119,7 +113,7 @@ namespace TrueCraft.Client
UpdateCamera();
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
(int)Client.Position.X, 0, (int)Client.Position.Z));
PendingMainThreadActions.Add(() => TransparentChunkMeshes.Sort(sorter));
PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter));
break;
}
}
@ -287,16 +281,11 @@ namespace TrueCraft.Client
i.Update(gameTime);
}
ChunkMesh mesh;
Mesh mesh;
if (IncomingChunks.TryTake(out mesh))
{
var existing = ChunkMeshes.SingleOrDefault(m => m.Chunk.Chunk.Coordinates == mesh.Chunk.Chunk.Coordinates);
if (existing != null)
ChunkMeshes.Remove(existing);
ChunkMeshes.Add(mesh);
}
if (IncomingTransparentChunks.TryTake(out mesh)) // TODO: re-render transparent meshes
TransparentChunkMeshes.Add(mesh);
Action action;
if (PendingMainThreadActions.TryTake(out action))
action();
@ -364,17 +353,17 @@ namespace TrueCraft.Client
{
verticies += ChunkMeshes[i].GetTotalVertices();
chunks++;
ChunkMeshes[i].Draw(OpaqueEffect);
ChunkMeshes[i].Draw(OpaqueEffect, 0);
}
}
GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
for (int i = 0; i < TransparentChunkMeshes.Count; i++)
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (CameraView.Intersects(TransparentChunkMeshes[i].BoundingBox))
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
{
if (!TransparentChunkMeshes[i].IsDisposed)
verticies += TransparentChunkMeshes[i].GetTotalVertices();
TransparentChunkMeshes[i].Draw(TransparentEffect);
if (!ChunkMeshes[i].IsDisposed)
verticies += ChunkMeshes[i].GetTotalVertices();
ChunkMeshes[i].Draw(TransparentEffect, 1);
}
}
GraphicsDevice.DepthStencilState = DepthStencilState.Default;

View File

@ -1,6 +1,8 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Express 2013 for Windows Desktop
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft", "TrueCraft\TrueCraft.csproj", "{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.API", "TrueCraft.API\TrueCraft.API.csproj", "{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}"
@ -19,44 +21,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCraft.Core.Test", "True
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.ActiveCfg = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.Build.0 = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.ActiveCfg = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.Build.0 = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.ActiveCfg = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.Build.0 = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.ActiveCfg = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.Build.0 = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.ActiveCfg = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.Build.0 = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.ActiveCfg = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.Build.0 = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.ActiveCfg = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.Build.0 = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.ActiveCfg = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.Build.0 = Release|Any CPU
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.ActiveCfg = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.Build.0 = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.ActiveCfg = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.Build.0 = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.ActiveCfg = Release|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.Build.0 = Release|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.ActiveCfg = Release|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.Build.0 = Release|x86
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.ActiveCfg = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.Build.0 = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.ActiveCfg = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.Build.0 = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.ActiveCfg = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.Build.0 = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.ActiveCfg = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.Build.0 = Release|Any CPU
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x64.ActiveCfg = Debug|x86
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x64.Build.0 = Debug|x86
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Debug|x86.ActiveCfg = Debug|x86
@ -65,14 +35,6 @@ Global
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x64.Build.0 = Release|x86
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x86.ActiveCfg = Release|x86
{C1C47EF5-2D8A-4231-AAA8-F651F52F480E}.Release|x86.Build.0 = Release|x86
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.ActiveCfg = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.Build.0 = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.ActiveCfg = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.Build.0 = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.ActiveCfg = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.Build.0 = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.ActiveCfg = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.Build.0 = Release|Any CPU
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x64.ActiveCfg = Debug|Any CPU
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x64.Build.0 = Debug|Any CPU
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Debug|x86.ActiveCfg = Debug|Any CPU
@ -81,6 +43,49 @@ Global
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x64.Build.0 = Release|Any CPU
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x86.ActiveCfg = Release|Any CPU
{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}.Release|x86.Build.0 = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.ActiveCfg = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x64.Build.0 = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.ActiveCfg = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Debug|x86.Build.0 = Debug|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.ActiveCfg = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x64.Build.0 = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.ActiveCfg = Release|Any CPU
{FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D}.Release|x86.Build.0 = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.ActiveCfg = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x64.Build.0 = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.ActiveCfg = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Debug|x86.Build.0 = Debug|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.ActiveCfg = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x64.Build.0 = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.ActiveCfg = Release|Any CPU
{4488498D-976D-4DA3-BF72-109531AF0488}.Release|x86.Build.0 = Release|Any CPU
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.ActiveCfg = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x64.Build.0 = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.ActiveCfg = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Debug|x86.Build.0 = Debug|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.ActiveCfg = Release|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x64.Build.0 = Release|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.ActiveCfg = Release|x86
{A6516869-A2FB-4E31-85C8-2285490CB32C}.Release|x86.Build.0 = Release|x86
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.ActiveCfg = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x64.Build.0 = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.ActiveCfg = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Debug|x86.Build.0 = Debug|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.ActiveCfg = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x64.Build.0 = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.ActiveCfg = Release|Any CPU
{6604F17A-552E-405D-B327-37C8B1648C86}.Release|x86.Build.0 = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.ActiveCfg = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x64.Build.0 = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.ActiveCfg = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Debug|x86.Build.0 = Debug|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.ActiveCfg = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x64.Build.0 = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.ActiveCfg = Release|Any CPU
{BCFDCD93-C23E-49E6-9767-A887B3C2A709}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BCFDCD93-C23E-49E6-9767-A887B3C2A709} = {6756B61E-5856-4CA7-90B5-6053763FE7BA}