diff --git a/OpenRA.Platforms.Default/ErrorHandler.cs b/OpenRA.Platforms.Default/ErrorHandler.cs index 97e6389ef3..855f4c6d42 100644 --- a/OpenRA.Platforms.Default/ErrorHandler.cs +++ b/OpenRA.Platforms.Default/ErrorHandler.cs @@ -14,7 +14,7 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Platforms.Default { - public static class ErrorHandler + static class ErrorHandler { public static void CheckGlVersion() { diff --git a/OpenRA.Platforms.Default/FrameBuffer.cs b/OpenRA.Platforms.Default/FrameBuffer.cs index d73625899d..a9d63d0e68 100644 --- a/OpenRA.Platforms.Default/FrameBuffer.cs +++ b/OpenRA.Platforms.Default/FrameBuffer.cs @@ -16,10 +16,10 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Platforms.Default { - public sealed class FrameBuffer : IFrameBuffer + sealed class FrameBuffer : ThreadAffine, IFrameBuffer { - Texture texture; - Size size; + readonly Texture texture; + readonly Size size; int framebuffer, depth; bool disposed; @@ -83,6 +83,8 @@ namespace OpenRA.Platforms.Default int[] cv = new int[4]; public void Bind() { + VerifyThreadAffinity(); + // Cache viewport rect to restore when unbinding cv = ViewportRectangle(); @@ -100,6 +102,7 @@ namespace OpenRA.Platforms.Default public void Unbind() { + VerifyThreadAffinity(); GL.Flush(); ErrorHandler.CheckGlError(); GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); @@ -108,7 +111,14 @@ namespace OpenRA.Platforms.Default ErrorHandler.CheckGlError(); } - public ITexture Texture { get { return texture; } } + public ITexture Texture + { + get + { + VerifyThreadAffinity(); + return texture; + } + } ~FrameBuffer() { diff --git a/OpenRA.Platforms.Default/MultiTapDetection.cs b/OpenRA.Platforms.Default/MultiTapDetection.cs index 6ff6f9f918..48c66ac6ac 100644 --- a/OpenRA.Platforms.Default/MultiTapDetection.cs +++ b/OpenRA.Platforms.Default/MultiTapDetection.cs @@ -13,7 +13,7 @@ using OpenRA.Primitives; namespace OpenRA.Platforms.Default { - public static class MultiTapDetection + static class MultiTapDetection { static Cache keyHistoryCache = new Cache(_ => new TapHistory(DateTime.Now - TimeSpan.FromSeconds(1))); diff --git a/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj b/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj index eb3f1b58d7..18b52071b6 100644 --- a/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj +++ b/OpenRA.Platforms.Default/OpenRA.Platforms.Default.csproj @@ -50,6 +50,7 @@ + diff --git a/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs b/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs index 71d2b89de0..7bf0f0d3d6 100644 --- a/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs +++ b/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs @@ -12,6 +12,7 @@ using System; using System.Drawing; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using OpenRA; using OpenRA.Graphics; using OpenTK.Graphics; @@ -20,20 +21,18 @@ using SDL2; namespace OpenRA.Platforms.Default { - public sealed class Sdl2GraphicsDevice : IGraphicsDevice + sealed class Sdl2GraphicsDevice : ThreadAffine, IGraphicsDevice { - Size size; - Sdl2Input input; + readonly Sdl2Input input; IntPtr context, window; bool disposed; - public Size WindowSize { get { return size; } } + public Size WindowSize { get; private set; } public Sdl2GraphicsDevice(Size windowSize, WindowMode windowMode) { Console.WriteLine("Using SDL 2 with OpenGL renderer"); - - size = windowSize; + WindowSize = windowSize; SDL.SDL_Init(SDL.SDL_INIT_NOPARACHUTE | SDL.SDL_INIT_VIDEO); SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1); @@ -46,15 +45,16 @@ namespace OpenRA.Platforms.Default SDL.SDL_GetCurrentDisplayMode(0, out display); Console.WriteLine("Desktop resolution: {0}x{1}", display.w, display.h); - if (size.Width == 0 && size.Height == 0) + if (WindowSize.Width == 0 && WindowSize.Height == 0) { Console.WriteLine("No custom resolution provided, using desktop resolution"); - size = new Size(display.w, display.h); + WindowSize = new Size(display.w, display.h); } - Console.WriteLine("Using resolution: {0}x{1}", size.Width, size.Height); + Console.WriteLine("Using resolution: {0}x{1}", WindowSize.Width, WindowSize.Height); - window = SDL.SDL_CreateWindow("OpenRA", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, size.Width, size.Height, SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL); + window = SDL.SDL_CreateWindow("OpenRA", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, + WindowSize.Width, WindowSize.Height, SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL); if (Game.Settings.Game.LockMouseWindow) GrabWindowMouseFocus(); @@ -100,6 +100,7 @@ namespace OpenRA.Platforms.Default public IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot) { + VerifyThreadAffinity(); try { return new SDL2HardwareCursor(size, data, hotspot); @@ -112,6 +113,7 @@ namespace OpenRA.Platforms.Default public void SetHardwareCursor(IHardwareCursor cursor) { + VerifyThreadAffinity(); var c = cursor as SDL2HardwareCursor; if (c == null) SDL.SDL_ShowCursor((int)SDL.SDL_bool.SDL_FALSE); @@ -214,12 +216,14 @@ namespace OpenRA.Platforms.Default public void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices) { + VerifyThreadAffinity(); GL.DrawArrays(ModeFromPrimitiveType(pt), firstVertex, numVertices); ErrorHandler.CheckGlError(); } public void Clear() { + VerifyThreadAffinity(); GL.ClearColor(0, 0, 0, 1); ErrorHandler.CheckGlError(); GL.Clear(ClearBufferMask.ColorBufferBit); @@ -228,6 +232,7 @@ namespace OpenRA.Platforms.Default public void EnableDepthBuffer() { + VerifyThreadAffinity(); GL.Clear(ClearBufferMask.DepthBufferBit); ErrorHandler.CheckGlError(); GL.Enable(EnableCap.DepthTest); @@ -236,12 +241,14 @@ namespace OpenRA.Platforms.Default public void DisableDepthBuffer() { + VerifyThreadAffinity(); GL.Disable(EnableCap.DepthTest); ErrorHandler.CheckGlError(); } public void SetBlendMode(BlendMode mode) { + VerifyThreadAffinity(); GL.BlendEquation(BlendEquationMode.FuncAdd); ErrorHandler.CheckGlError(); @@ -290,23 +297,27 @@ namespace OpenRA.Platforms.Default public void GrabWindowMouseFocus() { + VerifyThreadAffinity(); SDL.SDL_SetWindowGrab(window, SDL.SDL_bool.SDL_TRUE); } public void ReleaseWindowMouseFocus() { + VerifyThreadAffinity(); SDL.SDL_SetWindowGrab(window, SDL.SDL_bool.SDL_FALSE); } public void EnableScissor(int left, int top, int width, int height) { + VerifyThreadAffinity(); + if (width < 0) width = 0; if (height < 0) height = 0; - GL.Scissor(left, size.Height - (top + height), width, height); + GL.Scissor(left, WindowSize.Height - (top + height), width, height); ErrorHandler.CheckGlError(); GL.Enable(EnableCap.ScissorTest); ErrorHandler.CheckGlError(); @@ -314,19 +325,21 @@ namespace OpenRA.Platforms.Default public void DisableScissor() { + VerifyThreadAffinity(); GL.Disable(EnableCap.ScissorTest); ErrorHandler.CheckGlError(); } public void SetLineWidth(float width) { + VerifyThreadAffinity(); GL.LineWidth(width); ErrorHandler.CheckGlError(); } public Bitmap TakeScreenshot() { - var rect = new Rectangle(Point.Empty, size); + var rect = new Rectangle(Point.Empty, WindowSize); var bitmap = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); var data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); @@ -349,13 +362,52 @@ namespace OpenRA.Platforms.Default return bitmap; } - public void Present() { SDL.SDL_GL_SwapWindow(window); } - public void PumpInput(IInputHandler inputHandler) { input.PumpInput(inputHandler); } - public string GetClipboardText() { return input.GetClipboardText(); } - public IVertexBuffer CreateVertexBuffer(int size) { return new VertexBuffer(size); } - public ITexture CreateTexture() { return new Texture(); } - public ITexture CreateTexture(Bitmap bitmap) { return new Texture(bitmap); } - public IFrameBuffer CreateFrameBuffer(Size s) { return new FrameBuffer(s); } - public IShader CreateShader(string name) { return new Shader(name); } + public void Present() + { + VerifyThreadAffinity(); + SDL.SDL_GL_SwapWindow(window); + } + + public void PumpInput(IInputHandler inputHandler) + { + VerifyThreadAffinity(); + input.PumpInput(inputHandler); + } + + public string GetClipboardText() + { + VerifyThreadAffinity(); + return input.GetClipboardText(); + } + + public IVertexBuffer CreateVertexBuffer(int size) + { + VerifyThreadAffinity(); + return new VertexBuffer(size); + } + + public ITexture CreateTexture() + { + VerifyThreadAffinity(); + return new Texture(); + } + + public ITexture CreateTexture(Bitmap bitmap) + { + VerifyThreadAffinity(); + return new Texture(bitmap); + } + + public IFrameBuffer CreateFrameBuffer(Size s) + { + VerifyThreadAffinity(); + return new FrameBuffer(s); + } + + public IShader CreateShader(string name) + { + VerifyThreadAffinity(); + return new Shader(name); + } } } diff --git a/OpenRA.Platforms.Default/Sdl2Input.cs b/OpenRA.Platforms.Default/Sdl2Input.cs index 44ccd0c7ac..2582876ffc 100644 --- a/OpenRA.Platforms.Default/Sdl2Input.cs +++ b/OpenRA.Platforms.Default/Sdl2Input.cs @@ -15,7 +15,7 @@ using SDL2; namespace OpenRA.Platforms.Default { - public class Sdl2Input + class Sdl2Input { MouseButton lastButtonBits = (MouseButton)0; diff --git a/OpenRA.Platforms.Default/Shader.cs b/OpenRA.Platforms.Default/Shader.cs index 6e24a9692d..2414aa3604 100644 --- a/OpenRA.Platforms.Default/Shader.cs +++ b/OpenRA.Platforms.Default/Shader.cs @@ -17,11 +17,11 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Platforms.Default { - public class Shader : IShader + class Shader : ThreadAffine, IShader { readonly Dictionary samplers = new Dictionary(); readonly Dictionary textures = new Dictionary(); - int program; + readonly int program; protected int CompileShaderObject(ShaderType type, string name) { @@ -122,6 +122,7 @@ namespace OpenRA.Platforms.Default public void Render(Action a) { + VerifyThreadAffinity(); GL.UseProgram(program); // bind the textures @@ -138,6 +139,7 @@ namespace OpenRA.Platforms.Default public void SetTexture(string name, ITexture t) { + VerifyThreadAffinity(); if (t == null) return; @@ -148,6 +150,7 @@ namespace OpenRA.Platforms.Default public void SetVec(string name, float x) { + VerifyThreadAffinity(); GL.UseProgram(program); ErrorHandler.CheckGlError(); var param = GL.GetUniformLocation(program, name); @@ -158,6 +161,7 @@ namespace OpenRA.Platforms.Default public void SetVec(string name, float x, float y) { + VerifyThreadAffinity(); GL.UseProgram(program); ErrorHandler.CheckGlError(); var param = GL.GetUniformLocation(program, name); @@ -168,6 +172,7 @@ namespace OpenRA.Platforms.Default public void SetVec(string name, float[] vec, int length) { + VerifyThreadAffinity(); var param = GL.GetUniformLocation(program, name); ErrorHandler.CheckGlError(); switch (length) @@ -184,6 +189,7 @@ namespace OpenRA.Platforms.Default public void SetMatrix(string name, float[] mtx) { + VerifyThreadAffinity(); if (mtx.Length != 16) throw new InvalidDataException("Invalid 4x4 matrix"); diff --git a/OpenRA.Platforms.Default/Texture.cs b/OpenRA.Platforms.Default/Texture.cs index 62946b0d96..fe8847c458 100644 --- a/OpenRA.Platforms.Default/Texture.cs +++ b/OpenRA.Platforms.Default/Texture.cs @@ -16,15 +16,13 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Platforms.Default { - public sealed class Texture : ITexture + sealed class Texture : ThreadAffine, ITexture { int texture; TextureScaleFilter scaleFilter; - Size size; public int ID { get { return texture; } } - - public Size Size { get { return size; } } + public Size Size { get; private set; } bool disposed; @@ -37,6 +35,7 @@ namespace OpenRA.Platforms.Default set { + VerifyThreadAffinity(); if (scaleFilter == value) return; @@ -83,10 +82,11 @@ namespace OpenRA.Platforms.Default public void SetData(byte[] colors, int width, int height) { + VerifyThreadAffinity(); if (!Exts.IsPowerOf2(width) || !Exts.IsPowerOf2(height)) throw new InvalidDataException("Non-power-of-two array {0}x{1}".F(width, height)); - size = new Size(width, height); + Size = new Size(width, height); unsafe { fixed (byte* ptr = &colors[0]) @@ -103,13 +103,14 @@ namespace OpenRA.Platforms.Default // An array of RGBA public void SetData(uint[,] colors) { + VerifyThreadAffinity(); var width = colors.GetUpperBound(1) + 1; var height = colors.GetUpperBound(0) + 1; if (!Exts.IsPowerOf2(width) || !Exts.IsPowerOf2(height)) throw new InvalidDataException("Non-power-of-two array {0}x{1}".F(width, height)); - size = new Size(width, height); + Size = new Size(width, height); unsafe { fixed (uint* ptr = &colors[0, 0]) @@ -125,6 +126,7 @@ namespace OpenRA.Platforms.Default public void SetData(Bitmap bitmap) { + VerifyThreadAffinity(); var allocatedBitmap = false; if (!Exts.IsPowerOf2(bitmap.Width) || !Exts.IsPowerOf2(bitmap.Height)) { @@ -134,7 +136,7 @@ namespace OpenRA.Platforms.Default try { - size = new Size(bitmap.Width, bitmap.Height); + Size = new Size(bitmap.Width, bitmap.Height); var bits = bitmap.LockBits(bitmap.Bounds(), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); @@ -153,7 +155,8 @@ namespace OpenRA.Platforms.Default public byte[] GetData() { - var data = new byte[4 * size.Width * size.Height]; + VerifyThreadAffinity(); + var data = new byte[4 * Size.Width * Size.Height]; ErrorHandler.CheckGlError(); GL.BindTexture(TextureTarget.Texture2D, texture); @@ -172,10 +175,11 @@ namespace OpenRA.Platforms.Default public void SetEmpty(int width, int height) { + VerifyThreadAffinity(); if (!Exts.IsPowerOf2(width) || !Exts.IsPowerOf2(height)) throw new InvalidDataException("Non-power-of-two array {0}x{1}".F(width, height)); - size = new Size(width, height); + Size = new Size(width, height); PrepareTexture(); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, width, height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); diff --git a/OpenRA.Platforms.Default/ThreadAffine.cs b/OpenRA.Platforms.Default/ThreadAffine.cs new file mode 100644 index 0000000000..a2efba43e6 --- /dev/null +++ b/OpenRA.Platforms.Default/ThreadAffine.cs @@ -0,0 +1,31 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Threading; + +namespace OpenRA.Platforms.Default +{ + abstract class ThreadAffine + { + readonly int managedThreadId; + + protected ThreadAffine() + { + managedThreadId = Thread.CurrentThread.ManagedThreadId; + } + + 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."); + } + } +} diff --git a/OpenRA.Platforms.Default/VertexBuffer.cs b/OpenRA.Platforms.Default/VertexBuffer.cs index 8a7783aa94..1126fff3d0 100644 --- a/OpenRA.Platforms.Default/VertexBuffer.cs +++ b/OpenRA.Platforms.Default/VertexBuffer.cs @@ -14,7 +14,7 @@ using OpenTK.Graphics.OpenGL; namespace OpenRA.Platforms.Default { - public sealed class VertexBuffer : IVertexBuffer + sealed class VertexBuffer : ThreadAffine, IVertexBuffer where T : struct { static readonly int VertexSize = Marshal.SizeOf(typeof(T)); @@ -60,6 +60,7 @@ namespace OpenRA.Platforms.Default public void Bind() { + VerifyThreadAffinity(); GL.BindBuffer(BufferTarget.ArrayBuffer, buffer); ErrorHandler.CheckGlError(); GL.VertexPointer(3, VertexPointerType.Float, VertexSize, IntPtr.Zero); diff --git a/OpenRA.Platforms.Null/NullGraphicsDevice.cs b/OpenRA.Platforms.Null/NullGraphicsDevice.cs index e351859415..b6a9174b63 100644 --- a/OpenRA.Platforms.Null/NullGraphicsDevice.cs +++ b/OpenRA.Platforms.Null/NullGraphicsDevice.cs @@ -16,7 +16,7 @@ namespace OpenRA.Platforms.Null { public sealed class NullGraphicsDevice : IGraphicsDevice { - public Size WindowSize { get; internal set; } + public Size WindowSize { get; private set; } public NullGraphicsDevice(Size size, WindowMode window) {