This rule no longer appears to be buggy, so enforce it. Some of the automated fixes are adjusted in order to improve the result. #pragma directives have no option to control indentation, so remove them where possible.
814 lines
21 KiB
C#
814 lines
21 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading;
|
|
using OpenRA.Primitives;
|
|
|
|
namespace OpenRA.Platforms.Default
|
|
{
|
|
/// <summary>
|
|
/// Creates a dedicated thread for the graphics device. An internal message queue is used to perform actions on the
|
|
/// device. This allows calls to be enqueued to be processed asynchronously and thus free up the calling thread.
|
|
/// </summary>
|
|
sealed class ThreadedGraphicsContext : IGraphicsContext
|
|
{
|
|
// PERF: Maintain several object pools to reduce allocations.
|
|
readonly Dictionary<Type, object> vertexBufferPools = new();
|
|
readonly Stack<Message> messagePool = new();
|
|
readonly Queue<Message> messages = new();
|
|
|
|
public readonly int VertexBatchSize;
|
|
public readonly int IndexBatchSize;
|
|
readonly object syncObject = new();
|
|
readonly Thread renderThread;
|
|
volatile ExceptionDispatchInfo messageException;
|
|
|
|
// Delegates that perform actions on the real device.
|
|
Func<object> doClear;
|
|
Action doClearDepthBuffer;
|
|
Action doDisableDepthBuffer;
|
|
Action doEnableDepthBuffer;
|
|
Action doDisableScissor;
|
|
Action doPresent;
|
|
Func<string> getGLVersion;
|
|
Func<ITexture> getCreateTexture;
|
|
Func<object, IFrameBuffer> getCreateFrameBuffer;
|
|
Func<object, IShader> getCreateShader;
|
|
Func<object, object> getCreateVertexBuffer;
|
|
Func<object, IIndexBuffer> getCreateIndexBuffer;
|
|
Action<object> doDrawPrimitives;
|
|
Action<object> doDrawElements;
|
|
Action<object> doEnableScissor;
|
|
Action<object> doSetBlendMode;
|
|
Action<object> doSetVSync;
|
|
|
|
public ThreadedGraphicsContext(Sdl2GraphicsContext context, int vertexBatchSize, int indexBatchSize)
|
|
{
|
|
VertexBatchSize = vertexBatchSize;
|
|
IndexBatchSize = indexBatchSize;
|
|
renderThread = new Thread(RenderThread)
|
|
{
|
|
Name = "ThreadedGraphicsContext RenderThread",
|
|
IsBackground = true
|
|
};
|
|
lock (syncObject)
|
|
{
|
|
// Start and wait for the rendering thread to have initialized before returning.
|
|
// Otherwise, the delegates may not have been set yet.
|
|
renderThread.Start(context);
|
|
Monitor.Wait(syncObject);
|
|
}
|
|
}
|
|
|
|
void RenderThread(object contextObject)
|
|
{
|
|
using (var context = (Sdl2GraphicsContext)contextObject)
|
|
{
|
|
// This lock allows the constructor to block until initialization completes.
|
|
lock (syncObject)
|
|
{
|
|
context.InitializeOpenGL();
|
|
|
|
doClear = () => { context.Clear(); return null; };
|
|
doClearDepthBuffer = () => context.ClearDepthBuffer();
|
|
doDisableDepthBuffer = () => context.DisableDepthBuffer();
|
|
doEnableDepthBuffer = () => context.EnableDepthBuffer();
|
|
doDisableScissor = () => context.DisableScissor();
|
|
doPresent = () => context.Present();
|
|
getGLVersion = () => context.GLVersion;
|
|
getCreateTexture = () => new ThreadedTexture(this, (ITextureInternal)context.CreateTexture());
|
|
getCreateFrameBuffer =
|
|
tuple =>
|
|
{
|
|
var t = ((Size, Color))tuple;
|
|
return new ThreadedFrameBuffer(this,
|
|
context.CreateFrameBuffer(t.Item1, (ITextureInternal)CreateTexture(), t.Item2));
|
|
};
|
|
getCreateShader = bindings => new ThreadedShader(this, context.CreateShader((IShaderBindings)bindings));
|
|
getCreateVertexBuffer =
|
|
tuple =>
|
|
{
|
|
(object t, var type) = ((int, Type))tuple;
|
|
var vertexBuffer = context.GetType().GetMethod(nameof(CreateVertexBuffer)).MakeGenericMethod(type).Invoke(context, new[] { t });
|
|
return typeof(ThreadedVertexBuffer<>).MakeGenericType(type).GetConstructors()[0].Invoke(new[] { this, vertexBuffer });
|
|
};
|
|
getCreateIndexBuffer = indices => new ThreadedIndexBuffer(this, context.CreateIndexBuffer((uint[])indices));
|
|
doDrawPrimitives =
|
|
tuple =>
|
|
{
|
|
var t = ((PrimitiveType, int, int))tuple;
|
|
context.DrawPrimitives(t.Item1, t.Item2, t.Item3);
|
|
};
|
|
doDrawElements =
|
|
tuple =>
|
|
{
|
|
var t = ((int, int))tuple;
|
|
context.DrawElements(t.Item1, t.Item2);
|
|
};
|
|
doEnableScissor =
|
|
tuple =>
|
|
{
|
|
var t = ((int, int, int, int))tuple;
|
|
context.EnableScissor(t.Item1, t.Item2, t.Item3, t.Item4);
|
|
};
|
|
doSetBlendMode = mode => context.SetBlendMode((BlendMode)mode);
|
|
doSetVSync = enabled => context.SetVSyncEnabled((bool)enabled);
|
|
|
|
Monitor.Pulse(syncObject);
|
|
}
|
|
|
|
// Run a message loop.
|
|
// Only this rendering thread can perform actions on the real device,
|
|
// so other threads must send us a message which we process here.
|
|
Message message;
|
|
while (true)
|
|
{
|
|
lock (messages)
|
|
{
|
|
if (messages.Count == 0)
|
|
{
|
|
if (messageException != null)
|
|
break;
|
|
|
|
Monitor.Wait(messages);
|
|
}
|
|
|
|
message = messages.Dequeue();
|
|
}
|
|
|
|
if (message == null)
|
|
break;
|
|
|
|
message.Execute();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal T[] GetVertices<T>(int size)
|
|
{
|
|
lock (vertexBufferPools)
|
|
{
|
|
Stack<T[]> pool;
|
|
if (!vertexBufferPools.TryGetValue(typeof(T), out var poolObject))
|
|
{
|
|
pool = new Stack<T[]>();
|
|
vertexBufferPools.Add(typeof(T), pool);
|
|
}
|
|
else
|
|
pool = (Stack<T[]>)poolObject;
|
|
|
|
if (size <= VertexBatchSize && pool.Count > 0)
|
|
return pool.Pop();
|
|
}
|
|
|
|
return new T[size < VertexBatchSize ? VertexBatchSize : size];
|
|
}
|
|
|
|
internal void ReturnVertices<T>(T[] vertices)
|
|
{
|
|
if (vertices.Length == VertexBatchSize)
|
|
lock (vertexBufferPools)
|
|
((Stack<T[]>)vertexBufferPools[typeof(T)]).Push(vertices);
|
|
}
|
|
|
|
sealed class Message
|
|
{
|
|
public Message(ThreadedGraphicsContext device)
|
|
{
|
|
this.device = device;
|
|
}
|
|
|
|
readonly AutoResetEvent completed = new(false);
|
|
readonly ThreadedGraphicsContext device;
|
|
volatile Action action;
|
|
volatile Action<object> actionWithParam;
|
|
volatile Func<object> func;
|
|
volatile Func<object, object> funcWithParam;
|
|
volatile object param;
|
|
volatile object result;
|
|
volatile ExceptionDispatchInfo edi;
|
|
|
|
public void SetAction(Action method)
|
|
{
|
|
action = method;
|
|
}
|
|
|
|
public void SetAction(Action<object> method, object state)
|
|
{
|
|
actionWithParam = method;
|
|
param = state;
|
|
}
|
|
|
|
public void SetAction(Func<object> method)
|
|
{
|
|
func = method;
|
|
}
|
|
|
|
public void SetAction(Func<object, object> method, object state)
|
|
{
|
|
funcWithParam = method;
|
|
param = state;
|
|
}
|
|
|
|
public void Execute()
|
|
{
|
|
var wasSend = action != null || actionWithParam != null;
|
|
try
|
|
{
|
|
if (action != null)
|
|
{
|
|
action();
|
|
result = null;
|
|
action = null;
|
|
}
|
|
else if (actionWithParam != null)
|
|
{
|
|
actionWithParam(param);
|
|
result = null;
|
|
actionWithParam = null;
|
|
param = null;
|
|
}
|
|
else if (func != null)
|
|
{
|
|
result = func();
|
|
func = null;
|
|
}
|
|
else
|
|
{
|
|
result = funcWithParam(param);
|
|
funcWithParam = null;
|
|
param = null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
edi = ExceptionDispatchInfo.Capture(ex);
|
|
|
|
if (wasSend)
|
|
device.messageException = edi;
|
|
|
|
result = null;
|
|
param = null;
|
|
action = null;
|
|
actionWithParam = null;
|
|
func = null;
|
|
funcWithParam = null;
|
|
}
|
|
|
|
if (wasSend)
|
|
{
|
|
lock (device.messagePool)
|
|
device.messagePool.Push(this);
|
|
}
|
|
else
|
|
{
|
|
completed.Set();
|
|
}
|
|
}
|
|
|
|
public object Result()
|
|
{
|
|
completed.WaitOne();
|
|
|
|
var localEdi = edi;
|
|
edi = null;
|
|
var localResult = result;
|
|
result = null;
|
|
|
|
localEdi?.Throw();
|
|
return localResult;
|
|
}
|
|
}
|
|
|
|
Message GetMessage()
|
|
{
|
|
lock (messagePool)
|
|
if (messagePool.Count > 0)
|
|
return messagePool.Pop();
|
|
|
|
return new Message(this);
|
|
}
|
|
|
|
void QueueMessage(Message message)
|
|
{
|
|
var exception = messageException;
|
|
exception?.Throw();
|
|
|
|
lock (messages)
|
|
{
|
|
messages.Enqueue(message);
|
|
if (messages.Count == 1)
|
|
Monitor.Pulse(messages);
|
|
}
|
|
}
|
|
|
|
object RunMessage(Message message)
|
|
{
|
|
QueueMessage(message);
|
|
var result = message.Result();
|
|
lock (messagePool)
|
|
messagePool.Push(message);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a message to the rendering thread.
|
|
/// This method blocks until the message is processed, and returns the result.
|
|
/// </summary>
|
|
public T Send<T>(Func<T> method) where T : class
|
|
{
|
|
if (renderThread == Thread.CurrentThread)
|
|
return method();
|
|
|
|
var message = GetMessage();
|
|
message.SetAction(method);
|
|
return (T)RunMessage(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a message to the rendering thread.
|
|
/// This method blocks until the message is processed, and returns the result.
|
|
/// </summary>
|
|
public T Send<T>(Func<object, T> method, object state) where T : class
|
|
{
|
|
if (renderThread == Thread.CurrentThread)
|
|
return method(state);
|
|
|
|
var message = GetMessage();
|
|
message.SetAction(method, state);
|
|
return (T)RunMessage(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Posts a message to the rendering thread.
|
|
/// This method then returns immediately and does not wait for the message to be processed.
|
|
/// </summary>
|
|
public void Post(Action method)
|
|
{
|
|
if (renderThread == Thread.CurrentThread)
|
|
{
|
|
method();
|
|
return;
|
|
}
|
|
|
|
var message = GetMessage();
|
|
message.SetAction(method);
|
|
QueueMessage(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Posts a message to the rendering thread.
|
|
/// This method then returns immediately and does not wait for the message to be processed.
|
|
/// </summary>
|
|
public void Post(Action<object> method, object state)
|
|
{
|
|
if (renderThread == Thread.CurrentThread)
|
|
{
|
|
method(state);
|
|
return;
|
|
}
|
|
|
|
var message = GetMessage();
|
|
message.SetAction(method, state);
|
|
QueueMessage(message);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Use a null message to signal the rendering thread to clean up, then wait for it to complete.
|
|
QueueMessage(null);
|
|
renderThread.Join();
|
|
}
|
|
|
|
public string GLVersion => Send(getGLVersion);
|
|
|
|
public void Clear()
|
|
{
|
|
// We send the clear even though we could just post it.
|
|
// This ensures all previous messages have been processed before we return.
|
|
// This prevents us from queuing up work faster than it can be processed if rendering is behind.
|
|
Send(doClear);
|
|
}
|
|
|
|
public void ClearDepthBuffer()
|
|
{
|
|
Post(doClearDepthBuffer);
|
|
}
|
|
|
|
public IFrameBuffer CreateFrameBuffer(Size s)
|
|
{
|
|
return Send(getCreateFrameBuffer, (s, Color.FromArgb(0)));
|
|
}
|
|
|
|
public IFrameBuffer CreateFrameBuffer(Size s, Color clearColor)
|
|
{
|
|
return Send(getCreateFrameBuffer, (s, clearColor));
|
|
}
|
|
|
|
public IShader CreateShader(IShaderBindings bindings)
|
|
{
|
|
return Send(getCreateShader, bindings);
|
|
}
|
|
|
|
public ITexture CreateTexture()
|
|
{
|
|
return Send(getCreateTexture);
|
|
}
|
|
|
|
public IVertexBuffer<T> CreateVertexBuffer<T>(int size) where T : struct
|
|
{
|
|
return (IVertexBuffer<T>)Send(getCreateVertexBuffer, (size, typeof(T)));
|
|
}
|
|
|
|
public IIndexBuffer CreateIndexBuffer(uint[] indices)
|
|
{
|
|
return Send(getCreateIndexBuffer, indices);
|
|
}
|
|
|
|
public T[] CreateVertices<T>(int size) where T : struct
|
|
{
|
|
return GetVertices<T>(size);
|
|
}
|
|
|
|
public void DisableDepthBuffer()
|
|
{
|
|
Post(doDisableDepthBuffer);
|
|
}
|
|
|
|
public void DisableScissor()
|
|
{
|
|
Post(doDisableScissor);
|
|
}
|
|
|
|
public void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices)
|
|
{
|
|
Post(doDrawPrimitives, (type, firstVertex, numVertices));
|
|
}
|
|
|
|
public void DrawElements(int numIndices, int offset)
|
|
{
|
|
Post(doDrawElements, (numIndices, offset));
|
|
}
|
|
|
|
public void EnableDepthBuffer()
|
|
{
|
|
Post(doEnableDepthBuffer);
|
|
}
|
|
|
|
public void EnableScissor(int left, int top, int width, int height)
|
|
{
|
|
Post(doEnableScissor, (left, top, width, height));
|
|
}
|
|
|
|
public void Present()
|
|
{
|
|
Post(doPresent);
|
|
}
|
|
|
|
public void SetBlendMode(BlendMode mode)
|
|
{
|
|
Post(doSetBlendMode, mode);
|
|
}
|
|
|
|
public void SetVSyncEnabled(bool enabled)
|
|
{
|
|
Post(doSetVSync, enabled);
|
|
}
|
|
}
|
|
|
|
sealed class ThreadedFrameBuffer : IFrameBuffer
|
|
{
|
|
readonly ThreadedGraphicsContext device;
|
|
readonly Func<ITexture> getTexture;
|
|
readonly Action bind;
|
|
readonly Action unbind;
|
|
readonly Action dispose;
|
|
readonly Action<object> enableScissor;
|
|
readonly Action disableScissor;
|
|
|
|
public ThreadedFrameBuffer(ThreadedGraphicsContext device, IFrameBuffer frameBuffer)
|
|
{
|
|
this.device = device;
|
|
getTexture = () => frameBuffer.Texture;
|
|
bind = frameBuffer.Bind;
|
|
unbind = frameBuffer.Unbind;
|
|
dispose = frameBuffer.Dispose;
|
|
|
|
enableScissor = rect => frameBuffer.EnableScissor((Rectangle)rect);
|
|
disableScissor = frameBuffer.DisableScissor;
|
|
}
|
|
|
|
public ITexture Texture => device.Send(getTexture);
|
|
|
|
public void Bind()
|
|
{
|
|
device.Post(bind);
|
|
}
|
|
|
|
public void Unbind()
|
|
{
|
|
device.Post(unbind);
|
|
}
|
|
|
|
public void EnableScissor(Rectangle rect)
|
|
{
|
|
device.Post(enableScissor, rect);
|
|
}
|
|
|
|
public void DisableScissor()
|
|
{
|
|
device.Post(disableScissor);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
device.Post(dispose);
|
|
}
|
|
}
|
|
|
|
sealed class ThreadedVertexBuffer<T> : IVertexBuffer<T> where T : struct
|
|
{
|
|
readonly ThreadedGraphicsContext device;
|
|
readonly Action bind;
|
|
readonly Action<object> setData1;
|
|
readonly Action<object> setData2;
|
|
readonly Func<object, object> setData3;
|
|
readonly Action dispose;
|
|
|
|
public ThreadedVertexBuffer(ThreadedGraphicsContext device, IVertexBuffer<T> vertexBuffer)
|
|
{
|
|
this.device = device;
|
|
bind = vertexBuffer.Bind;
|
|
setData1 = tuple =>
|
|
{
|
|
var t = ((T[], int))tuple;
|
|
vertexBuffer.SetData(t.Item1, t.Item2);
|
|
device.ReturnVertices(t.Item1);
|
|
};
|
|
|
|
setData2 = tuple =>
|
|
{
|
|
var t = ((T[], int, int, int))tuple;
|
|
vertexBuffer.SetData(t.Item1, t.Item2, t.Item3, t.Item4);
|
|
device.ReturnVertices(t.Item1);
|
|
};
|
|
|
|
setData3 = tuple => { setData2(tuple); return null; };
|
|
dispose = vertexBuffer.Dispose;
|
|
}
|
|
|
|
public void Bind()
|
|
{
|
|
device.Post(bind);
|
|
}
|
|
|
|
public void SetData(T[] vertices, int length)
|
|
{
|
|
var buffer = device.GetVertices<T>(length);
|
|
Array.Copy(vertices, buffer, length);
|
|
device.Post(setData1, (buffer, length));
|
|
}
|
|
|
|
/// <summary>
|
|
/// PERF: The vertices array is passed without copying to the render thread. Upon return `vertices` may reference another
|
|
/// array object of at least the same size - containing random values.
|
|
/// </summary>
|
|
public void SetData(ref T[] vertices, int length)
|
|
{
|
|
device.Post(setData1, (vertices, length));
|
|
vertices = device.GetVertices<T>(vertices.Length);
|
|
}
|
|
|
|
public void SetData(T[] vertices, int offset, int start, int length)
|
|
{
|
|
if (length <= device.VertexBatchSize)
|
|
{
|
|
// If we are able to use a buffer without allocation, post a message to avoid blocking.
|
|
var buffer = device.GetVertices<T>(length);
|
|
Array.Copy(vertices, offset, buffer, 0, length);
|
|
device.Post(setData2, (buffer, 0, start, length));
|
|
}
|
|
else
|
|
{
|
|
// If the length is too large for a buffer, send a message and block to avoid allocations.
|
|
device.Send(setData3, (vertices, offset, start, length));
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
device.Post(dispose);
|
|
}
|
|
}
|
|
|
|
sealed class ThreadedIndexBuffer : IIndexBuffer
|
|
{
|
|
readonly ThreadedGraphicsContext device;
|
|
readonly Action bind;
|
|
readonly Action dispose;
|
|
|
|
public ThreadedIndexBuffer(ThreadedGraphicsContext device, IIndexBuffer indexBuffer)
|
|
{
|
|
this.device = device;
|
|
bind = indexBuffer.Bind;
|
|
dispose = indexBuffer.Dispose;
|
|
}
|
|
|
|
public void Bind()
|
|
{
|
|
device.Post(bind);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
device.Post(dispose);
|
|
}
|
|
}
|
|
|
|
sealed class ThreadedTexture : ITextureInternal
|
|
{
|
|
readonly ThreadedGraphicsContext device;
|
|
readonly Func<object> getScaleFilter;
|
|
readonly Action<object> setScaleFilter;
|
|
readonly Func<object> getSize;
|
|
readonly Action<object> setEmpty;
|
|
readonly Func<byte[]> getData;
|
|
readonly Action<object> setData1;
|
|
readonly Func<object, object> setData2;
|
|
readonly Action<object> setData3;
|
|
readonly Func<object, object> setData4;
|
|
readonly Action<object> setData5;
|
|
readonly Action dispose;
|
|
|
|
public ThreadedTexture(ThreadedGraphicsContext device, ITextureInternal texture)
|
|
{
|
|
this.device = device;
|
|
ID = texture.ID;
|
|
getScaleFilter = () => texture.ScaleFilter;
|
|
setScaleFilter = value => texture.ScaleFilter = (TextureScaleFilter)value;
|
|
getSize = () => texture.Size;
|
|
setEmpty = tuple => { var t = ((int, int))tuple; texture.SetEmpty(t.Item1, t.Item2); };
|
|
getData = () => texture.GetData();
|
|
setData1 = tuple => { var t = ((byte[], int, int))tuple; texture.SetData(t.Item1, t.Item2, t.Item3); };
|
|
setData2 = tuple => { setData1(tuple); return null; };
|
|
setData3 = tuple => { var t = ((float[], int, int))tuple; texture.SetFloatData(t.Item1, t.Item2, t.Item3); };
|
|
setData4 = tuple => { setData3(tuple); return null; };
|
|
setData5 = rect => texture.SetDataFromReadBuffer((Rectangle)rect);
|
|
dispose = texture.Dispose;
|
|
}
|
|
|
|
public uint ID { get; }
|
|
|
|
public TextureScaleFilter ScaleFilter
|
|
{
|
|
get => (TextureScaleFilter)device.Send(getScaleFilter);
|
|
|
|
set => device.Post(setScaleFilter, value);
|
|
}
|
|
|
|
public Size Size => (Size)device.Send(getSize);
|
|
|
|
public void SetEmpty(int width, int height)
|
|
{
|
|
device.Post(setEmpty, (width, height));
|
|
}
|
|
|
|
public byte[] GetData()
|
|
{
|
|
return device.Send(getData);
|
|
}
|
|
|
|
public void SetData(byte[] colors, int width, int height)
|
|
{
|
|
// Objects 85000 bytes or more will be directly allocated in the Large Object Heap (LOH).
|
|
// https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap
|
|
if (colors.Length < 85000)
|
|
{
|
|
// If we are able to create a small array the GC can collect easily, post a message to avoid blocking.
|
|
var temp = new byte[colors.Length];
|
|
Array.Copy(colors, temp, temp.Length);
|
|
device.Post(setData1, (temp, width, height));
|
|
}
|
|
else
|
|
{
|
|
// If the length is large and would result in an array on the Large Object Heap (LOH),
|
|
// send a message and block to avoid LOH allocation as this requires a Gen2 collection.
|
|
device.Send(setData2, (colors, width, height));
|
|
}
|
|
}
|
|
|
|
public void SetFloatData(float[] data, int width, int height)
|
|
{
|
|
// Objects 85000 bytes or more will be directly allocated in the Large Object Heap (LOH).
|
|
// https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap
|
|
if (data.Length < 21250)
|
|
{
|
|
// If we are able to create a small array the GC can collect easily, post a message to avoid blocking.
|
|
var temp = new float[data.Length];
|
|
Array.Copy(data, temp, temp.Length);
|
|
device.Post(setData3, (temp, width, height));
|
|
}
|
|
else
|
|
{
|
|
// If the length is large and would result in an array on the Large Object Heap (LOH),
|
|
// send a message and block to avoid LOH allocation as this requires a Gen2 collection.
|
|
device.Send(setData4, (data, width, height));
|
|
}
|
|
}
|
|
|
|
public void SetDataFromReadBuffer(Rectangle rect)
|
|
{
|
|
device.Post(setData5, rect);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
device.Post(dispose);
|
|
}
|
|
}
|
|
|
|
sealed class ThreadedShader : IShader
|
|
{
|
|
readonly ThreadedGraphicsContext device;
|
|
readonly Action prepareRender;
|
|
readonly Action<object> setBool;
|
|
readonly Action<object> setMatrix;
|
|
readonly Action<object> setTexture;
|
|
readonly Action<object> setVec1;
|
|
readonly Action<object> setVec2;
|
|
readonly Action<object> setVec3;
|
|
readonly Action<object> setVec4;
|
|
readonly Action bind;
|
|
|
|
public ThreadedShader(ThreadedGraphicsContext device, IShader shader)
|
|
{
|
|
this.device = device;
|
|
bind = shader.Bind;
|
|
prepareRender = shader.PrepareRender;
|
|
setBool = tuple => { var t = ((string, bool))tuple; shader.SetBool(t.Item1, t.Item2); };
|
|
setMatrix = tuple => { var t = ((string, float[]))tuple; shader.SetMatrix(t.Item1, t.Item2); };
|
|
setTexture = tuple => { var t = ((string, ITexture))tuple; shader.SetTexture(t.Item1, t.Item2); };
|
|
setVec1 = tuple => { var t = ((string, float))tuple; shader.SetVec(t.Item1, t.Item2); };
|
|
setVec2 = tuple => { var t = ((string, float[], int))tuple; shader.SetVec(t.Item1, t.Item2, t.Item3); };
|
|
setVec3 = tuple => { var t = ((string, float, float))tuple; shader.SetVec(t.Item1, t.Item2, t.Item3); };
|
|
setVec4 = tuple => { var t = ((string, float, float, float))tuple; shader.SetVec(t.Item1, t.Item2, t.Item3, t.Item4); };
|
|
}
|
|
|
|
public void Bind()
|
|
{
|
|
device.Post(bind);
|
|
}
|
|
|
|
public void PrepareRender()
|
|
{
|
|
device.Post(prepareRender);
|
|
}
|
|
|
|
public void SetBool(string name, bool value)
|
|
{
|
|
device.Post(setBool, (name, value));
|
|
}
|
|
|
|
public void SetMatrix(string param, float[] mtx)
|
|
{
|
|
device.Post(setMatrix, (param, mtx));
|
|
}
|
|
|
|
public void SetTexture(string param, ITexture texture)
|
|
{
|
|
device.Post(setTexture, (param, texture));
|
|
}
|
|
|
|
public void SetVec(string name, float x)
|
|
{
|
|
device.Post(setVec1, (name, x));
|
|
}
|
|
|
|
public void SetVec(string name, float[] vec, int length)
|
|
{
|
|
device.Post(setVec2, (name, vec, length));
|
|
}
|
|
|
|
public void SetVec(string name, float x, float y)
|
|
{
|
|
device.Post(setVec3, (name, x, y));
|
|
}
|
|
|
|
public void SetVec(string name, float x, float y, float z)
|
|
{
|
|
device.Post(setVec4, (name, x, y, z));
|
|
}
|
|
}
|
|
}
|