diff --git a/OpenRA.Game/Graphics/PlatformInterfaces.cs b/OpenRA.Game/Graphics/PlatformInterfaces.cs index 1f1d6b4a27..bb035af768 100644 --- a/OpenRA.Game/Graphics/PlatformInterfaces.cs +++ b/OpenRA.Game/Graphics/PlatformInterfaces.cs @@ -17,7 +17,7 @@ namespace OpenRA { public interface IPlatform { - IPlatformWindow CreateWindow(Size size, WindowMode windowMode); + IPlatformWindow CreateWindow(Size size, WindowMode windowMode, int batchSize); ISoundEngine CreateSound(string device); } diff --git a/OpenRA.Game/Renderer.cs b/OpenRA.Game/Renderer.cs index 9b9b784dcc..c5e25f57af 100644 --- a/OpenRA.Game/Renderer.cs +++ b/OpenRA.Game/Renderer.cs @@ -53,7 +53,7 @@ namespace OpenRA { var resolution = GetResolution(graphicSettings); - Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode); + Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.BatchSize); Context = Window.Context; TempBufferSize = graphicSettings.BatchSize; @@ -95,8 +95,11 @@ namespace OpenRA Window.OnWindowScaleChanged += (before, after) => { - foreach (var f in Fonts) - f.Value.SetScale(after); + Game.RunAfterTick(() => + { + foreach (var f in Fonts) + f.Value.SetScale(after); + }); }; } @@ -263,7 +266,6 @@ namespace OpenRA public void Dispose() { - Window.Dispose(); WorldModelRenderer.Dispose(); tempBuffer.Dispose(); if (fontSheetBuilder != null) @@ -271,6 +273,7 @@ namespace OpenRA if (Fonts != null) foreach (var font in Fonts.Values) font.Dispose(); + Window.Dispose(); } public string GetClipboardText() diff --git a/OpenRA.Platforms.Default/DefaultPlatform.cs b/OpenRA.Platforms.Default/DefaultPlatform.cs index 9a6e5b0fff..6166803a79 100644 --- a/OpenRA.Platforms.Default/DefaultPlatform.cs +++ b/OpenRA.Platforms.Default/DefaultPlatform.cs @@ -16,9 +16,9 @@ namespace OpenRA.Platforms.Default { public class DefaultPlatform : IPlatform { - public IPlatformWindow CreateWindow(Size size, WindowMode windowMode) + public IPlatformWindow CreateWindow(Size size, WindowMode windowMode, int batchSize) { - return new Sdl2PlatformWindow(size, windowMode); + return new Sdl2PlatformWindow(size, windowMode, batchSize); } public ISoundEngine CreateSound(string device) diff --git a/OpenRA.Platforms.Default/FrameBuffer.cs b/OpenRA.Platforms.Default/FrameBuffer.cs index d2218dbb2c..10a5ae86c0 100644 --- a/OpenRA.Platforms.Default/FrameBuffer.cs +++ b/OpenRA.Platforms.Default/FrameBuffer.cs @@ -18,12 +18,12 @@ namespace OpenRA.Platforms.Default { sealed class FrameBuffer : ThreadAffine, IFrameBuffer { - readonly Texture texture; + readonly ITexture texture; readonly Size size; uint framebuffer, depth; bool disposed; - public FrameBuffer(Size size) + public FrameBuffer(Size size, ITextureInternal texture) { this.size = size; if (!Exts.IsPowerOf2(size.Width) || !Exts.IsPowerOf2(size.Height)) @@ -35,7 +35,7 @@ namespace OpenRA.Platforms.Default OpenGL.CheckGLError(); // Color - texture = new Texture(); + this.texture = texture; texture.SetEmpty(size.Width, size.Height); OpenGL.glFramebufferTexture2D(OpenGL.FRAMEBUFFER_EXT, OpenGL.COLOR_ATTACHMENT0_EXT, OpenGL.GL_TEXTURE_2D, texture.ID, 0); OpenGL.CheckGLError(); @@ -120,14 +120,9 @@ namespace OpenRA.Platforms.Default } } - ~FrameBuffer() - { - Game.RunAfterTick(() => Dispose(false)); - } - public void Dispose() { - Game.RunAfterTick(() => Dispose(true)); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/OpenRA.Platforms.Default/ITextureInternal.cs b/OpenRA.Platforms.Default/ITextureInternal.cs new file mode 100644 index 0000000000..03ce085a6d --- /dev/null +++ b/OpenRA.Platforms.Default/ITextureInternal.cs @@ -0,0 +1,19 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) + * 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 + +namespace OpenRA.Platforms.Default +{ + interface ITextureInternal : ITexture + { + uint ID { get; } + void SetEmpty(int width, int height); + } +} diff --git a/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj b/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj index 1f594d7dbd..1a2bb76055 100644 --- a/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj +++ b/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj @@ -49,12 +49,14 @@ + + diff --git a/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs b/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs index 394962dfd5..ac1a8ad322 100644 --- a/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs +++ b/OpenRA.Platforms.Default/Sdl2GraphicsContext.cs @@ -25,6 +25,12 @@ namespace OpenRA.Platforms.Default public Sdl2GraphicsContext(Sdl2PlatformWindow window) { this.window = window; + } + + internal void InitializeOpenGL() + { + SetThreadAffinity(); + context = SDL.SDL_GL_CreateContext(window.Window); if (context == IntPtr.Zero || SDL.SDL_GL_MakeCurrent(window.Window, context) < 0) throw new InvalidOperationException("Can not create OpenGL context. (Error: {0})".F(SDL.SDL_GetError())); @@ -60,7 +66,13 @@ namespace OpenRA.Platforms.Default public IFrameBuffer CreateFrameBuffer(Size s) { VerifyThreadAffinity(); - return new FrameBuffer(s); + return new FrameBuffer(s, new Texture()); + } + + public IFrameBuffer CreateFrameBuffer(Size s, ITextureInternal texture) + { + VerifyThreadAffinity(); + return new FrameBuffer(s, texture); } public IShader CreateShader(string name) diff --git a/OpenRA.Platforms.Default/Sdl2HardwareCursor.cs b/OpenRA.Platforms.Default/Sdl2HardwareCursor.cs index 9f83715e1e..b2261089ed 100644 --- a/OpenRA.Platforms.Default/Sdl2HardwareCursor.cs +++ b/OpenRA.Platforms.Default/Sdl2HardwareCursor.cs @@ -53,14 +53,9 @@ namespace OpenRA.Platforms.Default } } - ~Sdl2HardwareCursor() - { - Game.RunAfterTick(() => Dispose(false)); - } - public void Dispose() { - Game.RunAfterTick(() => Dispose(true)); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/OpenRA.Platforms.Default/Sdl2Input.cs b/OpenRA.Platforms.Default/Sdl2Input.cs index 48071d8555..6107728264 100644 --- a/OpenRA.Platforms.Default/Sdl2Input.cs +++ b/OpenRA.Platforms.Default/Sdl2Input.cs @@ -191,8 +191,6 @@ namespace OpenRA.Platforms.Default inputHandler.OnMouseInput(pendingMotion.Value); pendingMotion = null; } - - OpenGL.CheckGLError(); } } } diff --git a/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs b/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs index b9b83218eb..50b2c034ed 100644 --- a/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs +++ b/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs @@ -72,7 +72,7 @@ namespace OpenRA.Platforms.Default [DllImport("user32.dll")] static extern bool SetProcessDPIAware(); - public Sdl2PlatformWindow(Size requestWindowSize, WindowMode windowMode) + public Sdl2PlatformWindow(Size requestWindowSize, WindowMode windowMode, int batchSize) { Console.WriteLine("Using SDL 2 with OpenGL renderer"); @@ -184,7 +184,10 @@ namespace OpenRA.Platforms.Default } } - context = new Sdl2GraphicsContext(this); + // Run graphics rendering on a dedicated thread. + // The calling thread will then have more time to process other tasks, since rendering happens in parallel. + // If the calling thread is the main game thread, this means it can run more logic and render ticks. + context = new ThreadedGraphicsContext(new Sdl2GraphicsContext(this), batchSize); SDL.SDL_SetModState(SDL.SDL_Keymod.KMOD_NONE); input = new Sdl2Input(); diff --git a/OpenRA.Platforms.Default/Shader.cs b/OpenRA.Platforms.Default/Shader.cs index a373054887..c357d96f5a 100644 --- a/OpenRA.Platforms.Default/Shader.cs +++ b/OpenRA.Platforms.Default/Shader.cs @@ -139,7 +139,7 @@ namespace OpenRA.Platforms.Default foreach (var kv in textures) { OpenGL.glActiveTexture(OpenGL.GL_TEXTURE0 + kv.Key); - OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, ((Texture)kv.Value).ID); + OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, ((ITextureInternal)kv.Value).ID); } OpenGL.CheckGLError(); diff --git a/OpenRA.Platforms.Default/Texture.cs b/OpenRA.Platforms.Default/Texture.cs index 8b67ab3f16..36a03d307e 100644 --- a/OpenRA.Platforms.Default/Texture.cs +++ b/OpenRA.Platforms.Default/Texture.cs @@ -16,7 +16,7 @@ using System.IO; namespace OpenRA.Platforms.Default { - sealed class Texture : ThreadAffine, ITexture + sealed class Texture : ThreadAffine, ITextureInternal { uint texture; TextureScaleFilter scaleFilter; @@ -187,14 +187,9 @@ namespace OpenRA.Platforms.Default OpenGL.CheckGLError(); } - ~Texture() - { - Game.RunAfterTick(() => Dispose(false)); - } - public void Dispose() { - Game.RunAfterTick(() => Dispose(true)); + Dispose(true); GC.SuppressFinalize(this); } diff --git a/OpenRA.Platforms.Default/ThreadAffine.cs b/OpenRA.Platforms.Default/ThreadAffine.cs index 1a9b97e027..f3f5562025 100644 --- a/OpenRA.Platforms.Default/ThreadAffine.cs +++ b/OpenRA.Platforms.Default/ThreadAffine.cs @@ -16,9 +16,14 @@ namespace OpenRA.Platforms.Default { abstract class ThreadAffine { - readonly int managedThreadId; + volatile int managedThreadId; protected ThreadAffine() + { + SetThreadAffinity(); + } + + protected void SetThreadAffinity() { managedThreadId = Thread.CurrentThread.ManagedThreadId; } @@ -26,7 +31,7 @@ namespace OpenRA.Platforms.Default protected void VerifyThreadAffinity() { if (managedThreadId != Thread.CurrentThread.ManagedThreadId) - throw new InvalidOperationException("Cross-thread operation not valid: This method must be called from the same thread that created this object."); + throw new InvalidOperationException("Cross-thread operation not valid: This method must only be called from the thread that owns this object."); } } } diff --git a/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs b/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs new file mode 100644 index 0000000000..47ed4eff28 --- /dev/null +++ b/OpenRA.Platforms.Default/ThreadedGraphicsContext.cs @@ -0,0 +1,702 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) + * 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.Drawing; +using System.Runtime.ExceptionServices; +using System.Threading; +using OpenRA.Graphics; + +namespace OpenRA.Platforms.Default +{ + /// + /// 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. + /// + sealed class ThreadedGraphicsContext : IGraphicsContext + { + // PERF: Maintain several object pools to reduce allocations. + readonly Stack verticesPool = new Stack(); + readonly Stack messagePool = new Stack(); + readonly Queue messages = new Queue(); + + readonly object syncObject = new object(); + readonly Thread renderThread; + readonly int batchSize; + volatile ExceptionDispatchInfo messageException; + + // Delegates that perform actions on the real device. + Func doClear; + Action doClearDepthBuffer; + Action doDisableDepthBuffer; + Action doEnableDepthBuffer; + Action doDisableScissor; + Action doPresent; + Func getGLVersion; + Func getCreateTexture; + Func getCreateTextureBitmap; + Func getTakeScreenshot; + Func getCreateFrameBuffer; + Func getCreateShader; + Func> getCreateVertexBuffer; + Action doDrawPrimitives; + Action doEnableScissor; + Action doSetBlendMode; + + public ThreadedGraphicsContext(Sdl2GraphicsContext context, int batchSize) + { + this.batchSize = batchSize; + renderThread = new Thread(RenderThread) + { + Name = "ThreadedGraphicsContext RenderThread", + IsBackground = true + }; + renderThread.SetApartmentState(ApartmentState.STA); + 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()); + getCreateTextureBitmap = bitmap => new ThreadedTexture(this, (ITextureInternal)context.CreateTexture((Bitmap)bitmap)); + getTakeScreenshot = () => context.TakeScreenshot(); + getCreateFrameBuffer = s => new ThreadedFrameBuffer(this, context.CreateFrameBuffer((Size)s, (ITextureInternal)CreateTexture())); + getCreateShader = name => new ThreadedShader(this, context.CreateShader((string)name)); + getCreateVertexBuffer = length => new ThreadedVertexBuffer(this, context.CreateVertexBuffer((int)length)); + doDrawPrimitives = + tuple => + { + var t = (Tuple)tuple; + context.DrawPrimitives(t.Item1, t.Item2, t.Item3); + }; + doEnableScissor = + tuple => + { + var t = (Tuple)tuple; + context.EnableScissor(t.Item1, t.Item2, t.Item3, t.Item4); + }; + doSetBlendMode = mode => { context.SetBlendMode((BlendMode)mode); }; + + 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 Vertex[] GetVertices(int size) + { + lock (verticesPool) + if (size <= batchSize && verticesPool.Count > 0) + return verticesPool.Pop(); + + return new Vertex[size < batchSize ? batchSize : size]; + } + + internal void ReturnVertices(Vertex[] vertices) + { + if (vertices.Length == batchSize) + lock (verticesPool) + verticesPool.Push(vertices); + } + + class Message + { + public Message(ThreadedGraphicsContext device) + { + this.device = device; + } + + readonly AutoResetEvent completed = new AutoResetEvent(false); + readonly ThreadedGraphicsContext device; + volatile Action action; + volatile Action actionWithParam; + volatile Func func; + volatile Func funcWithParam; + volatile object param; + volatile object result; + volatile ExceptionDispatchInfo edi; + + public void SetAction(Action method) + { + action = method; + } + + public void SetAction(Action method, object state) + { + actionWithParam = method; + param = state; + } + + public void SetAction(Func method) + { + func = method; + } + + public void SetAction(Func 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; + + if (localEdi != 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; + if (exception != null) + 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; + } + + /// + /// Sends a message to the rendering thread. + /// This method blocks until the message is processed, and returns the result. + /// + public T Send(Func method) where T : class + { + if (renderThread == Thread.CurrentThread) + return method(); + + var message = GetMessage(); + message.SetAction(method); + return (T)RunMessage(message); + } + + /// + /// Sends a message to the rendering thread. + /// This method blocks until the message is processed, and returns the result. + /// + public T Send(Func method, object state) where T : class + { + if (renderThread == Thread.CurrentThread) + return method(state); + + var message = GetMessage(); + message.SetAction(method, state); + return (T)RunMessage(message); + } + + /// + /// Posts a message to the rendering thread. + /// This method then returns immediately and does not wait for the message to be processed. + /// + public void Post(Action method) + { + if (renderThread == Thread.CurrentThread) + { + method(); + return; + } + + var message = GetMessage(); + message.SetAction(method); + QueueMessage(message); + } + + /// + /// Posts a message to the rendering thread. + /// This method then returns immediately and does not wait for the message to be processed. + /// + public void Post(Action 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 + { + get + { + return 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); + } + + public IShader CreateShader(string name) + { + return Send(getCreateShader, name); + } + + public ITexture CreateTexture() + { + return Send(getCreateTexture); + } + + public ITexture CreateTexture(Bitmap bitmap) + { + return Send(getCreateTextureBitmap, bitmap); + } + + public IVertexBuffer CreateVertexBuffer(int length) + { + return Send(getCreateVertexBuffer, length); + } + + public void DisableDepthBuffer() + { + Post(doDisableDepthBuffer); + } + + public void DisableScissor() + { + Post(doDisableScissor); + } + + public void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices) + { + Post(doDrawPrimitives, Tuple.Create(type, firstVertex, numVertices)); + } + + public void EnableDepthBuffer() + { + Post(doEnableDepthBuffer); + } + + public void EnableScissor(int left, int top, int width, int height) + { + Post(doEnableScissor, Tuple.Create(left, top, width, height)); + } + + public void Present() + { + Post(doPresent); + } + + public void SetBlendMode(BlendMode mode) + { + Post(doSetBlendMode, mode); + } + + public Bitmap TakeScreenshot() + { + return Send(getTakeScreenshot); + } + } + + class ThreadedFrameBuffer : IFrameBuffer + { + readonly ThreadedGraphicsContext device; + readonly Func getTexture; + readonly Action bind; + readonly Action unbind; + readonly Action dispose; + + public ThreadedFrameBuffer(ThreadedGraphicsContext device, IFrameBuffer frameBuffer) + { + this.device = device; + getTexture = () => frameBuffer.Texture; + bind = frameBuffer.Bind; + unbind = frameBuffer.Unbind; + dispose = frameBuffer.Dispose; + } + + public ITexture Texture + { + get + { + return device.Send(getTexture); + } + } + + public void Bind() + { + device.Post(bind); + } + + public void Unbind() + { + device.Post(unbind); + } + + public void Dispose() + { + device.Post(dispose); + } + } + + class ThreadedVertexBuffer : IVertexBuffer + { + readonly ThreadedGraphicsContext device; + readonly Action bind; + readonly Action setData1; + readonly Func setData2; + readonly Action dispose; + + public ThreadedVertexBuffer(ThreadedGraphicsContext device, IVertexBuffer vertexBuffer) + { + this.device = device; + bind = vertexBuffer.Bind; + setData1 = tuple => { var t = (Tuple)tuple; vertexBuffer.SetData(t.Item1, t.Item2); device.ReturnVertices(t.Item1); }; + setData2 = tuple => { var t = (Tuple)tuple; vertexBuffer.SetData(t.Item1, t.Item2, t.Item3); return null; }; + dispose = vertexBuffer.Dispose; + } + + public void Bind() + { + device.Post(bind); + } + + public void SetData(Vertex[] vertices, int length) + { + var buffer = device.GetVertices(length); + Array.Copy(vertices, buffer, length); + device.Post(setData1, Tuple.Create(buffer, length)); + } + + public void SetData(IntPtr data, int start, int length) + { + // We can't return until we are finished with the data, so we must Send here. + device.Send(setData2, Tuple.Create(data, start, length)); + } + + public void SetData(Vertex[] vertices, int start, int length) + { + var buffer = device.GetVertices(length); + Array.Copy(vertices, start, buffer, 0, length); + device.Post(setData1, Tuple.Create(buffer, length)); + } + + public void Dispose() + { + device.Post(dispose); + } + } + + class ThreadedTexture : ITextureInternal + { + readonly ThreadedGraphicsContext device; + readonly uint id; + readonly Func getScaleFilter; + readonly Action setScaleFilter; + readonly Func getSize; + readonly Action setEmpty; + readonly Func getData; + readonly Func setData1; + readonly Func setData2; + readonly Action setData3; + 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 = (Tuple)tuple; texture.SetEmpty(t.Item1, t.Item2); }; + getData = () => texture.GetData(); + setData1 = colors => { texture.SetData((uint[,])colors); return null; }; + setData2 = bitmap => { texture.SetData((Bitmap)bitmap); return null; }; + setData3 = tuple => { var t = (Tuple)tuple; texture.SetData(t.Item1, t.Item2, t.Item3); }; + dispose = texture.Dispose; + } + + public uint ID + { + get + { + return id; + } + } + + public TextureScaleFilter ScaleFilter + { + get + { + return (TextureScaleFilter)device.Send(getScaleFilter); + } + + set + { + device.Post(setScaleFilter, value); + } + } + + public Size Size + { + get + { + return (Size)device.Send(getSize); + } + } + + public void SetEmpty(int width, int height) + { + device.Post(setEmpty, Tuple.Create(width, height)); + } + + public byte[] GetData() + { + return device.Send(getData); + } + + public void SetData(uint[,] colors) + { + // We can't return until we are finished with the data, so we must Send here. + device.Send(setData1, colors); + } + + public void SetData(Bitmap bitmap) + { + // We can't return until we are finished with the data, so we must Send here. + device.Send(setData2, bitmap); + } + + public void SetData(byte[] colors, int width, int height) + { + // This creates some garbage for the GC to clean up, + // but allows us post a message instead of blocking the message queue by sending it. + var temp = new byte[colors.Length]; + Array.Copy(colors, temp, temp.Length); + device.Post(setData3, Tuple.Create(temp, width, height)); + } + + public void Dispose() + { + device.Post(dispose); + } + } + + class ThreadedShader : IShader + { + readonly ThreadedGraphicsContext device; + readonly Action prepareRender; + readonly Action setBool; + readonly Action setMatrix; + readonly Action setTexture; + readonly Action setVec1; + readonly Action setVec2; + readonly Action setVec3; + readonly Action setVec4; + + public ThreadedShader(ThreadedGraphicsContext device, IShader shader) + { + this.device = device; + prepareRender = shader.PrepareRender; + setBool = tuple => { var t = (Tuple)tuple; shader.SetBool(t.Item1, t.Item2); }; + setMatrix = tuple => { var t = (Tuple)tuple; shader.SetMatrix(t.Item1, t.Item2); }; + setTexture = tuple => { var t = (Tuple)tuple; shader.SetTexture(t.Item1, t.Item2); }; + setVec1 = tuple => { var t = (Tuple)tuple; shader.SetVec(t.Item1, t.Item2); }; + setVec2 = tuple => { var t = (Tuple)tuple; shader.SetVec(t.Item1, t.Item2, t.Item3); }; + setVec3 = tuple => { var t = (Tuple)tuple; shader.SetVec(t.Item1, t.Item2, t.Item3); }; + setVec4 = tuple => { var t = (Tuple)tuple; shader.SetVec(t.Item1, t.Item2, t.Item3, t.Item4); }; + } + + public void PrepareRender() + { + device.Post(prepareRender); + } + + public void SetBool(string name, bool value) + { + device.Post(setBool, Tuple.Create(name, value)); + } + + public void SetMatrix(string param, float[] mtx) + { + device.Post(setMatrix, Tuple.Create(param, mtx)); + } + + public void SetTexture(string param, ITexture texture) + { + device.Post(setTexture, Tuple.Create(param, texture)); + } + + public void SetVec(string name, float x) + { + device.Post(setVec1, Tuple.Create(name, x)); + } + + public void SetVec(string name, float[] vec, int length) + { + device.Post(setVec2, Tuple.Create(name, vec, length)); + } + + public void SetVec(string name, float x, float y) + { + device.Post(setVec3, Tuple.Create(name, x, y)); + } + + public void SetVec(string name, float x, float y, float z) + { + device.Post(setVec4, Tuple.Create(name, x, y, z)); + } + } +} \ No newline at end of file diff --git a/OpenRA.Platforms.Default/VertexBuffer.cs b/OpenRA.Platforms.Default/VertexBuffer.cs index 7f43077005..5acbf5ac0c 100644 --- a/OpenRA.Platforms.Default/VertexBuffer.cs +++ b/OpenRA.Platforms.Default/VertexBuffer.cs @@ -103,14 +103,9 @@ namespace OpenRA.Platforms.Default OpenGL.CheckGLError(); } - ~VertexBuffer() - { - Game.RunAfterTick(() => Dispose(false)); - } - public void Dispose() { - Game.RunAfterTick(() => Dispose(true)); + Dispose(true); GC.SuppressFinalize(this); }