From 649f81494e4b5db22f1243e87f7401c05ba4da07 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 23 Sep 2014 21:37:46 +0200 Subject: [PATCH] Fix some race conditions with the texture references Add queuing of files Split CEncoding to separate file --- Vocaluxe/Base/CDraw.cs | 13 +- Vocaluxe/Lib/Draw/CDirect3D.cs | 24 +- Vocaluxe/Lib/Draw/CDrawBase.cs | 449 ++++++++++++++++++++------ Vocaluxe/Lib/Draw/CDrawBaseWindows.cs | 4 +- Vocaluxe/Lib/Draw/CDrawWinForm.cs | 5 + Vocaluxe/Lib/Draw/COpenGL.cs | 42 +-- Vocaluxe/Lib/Draw/CTextureBase.cs | 72 ++--- Vocaluxe/Lib/Draw/IDraw.cs | 9 +- VocaluxeLib/CEncoding.cs | 39 +++ VocaluxeLib/CHelper.cs | 51 ++- VocaluxeLib/VocaluxeLib.csproj | 1 + 11 files changed, 513 insertions(+), 196 deletions(-) create mode 100644 VocaluxeLib/CEncoding.cs diff --git a/Vocaluxe/Base/CDraw.cs b/Vocaluxe/Base/CDraw.cs index 7cb2e463..ee71bf34 100644 --- a/Vocaluxe/Base/CDraw.cs +++ b/Vocaluxe/Base/CDraw.cs @@ -196,13 +196,24 @@ namespace Vocaluxe.Base /// Requests adding a texture from a bitmap
/// Use this if you add textures from another thread or don't need it immediatelly /// - /// + /// Bitmap to add, gets disposed after adding! /// Reference to texture public static CTextureRef EnqueueTexture(Bitmap bmp) { return _Draw.EnqueueTexture(bmp); } + /// + /// Requests adding a texture from a file
+ /// Use this if you add textures from another thread or don't need it immediatelly + ///
+ /// Full path to the image file + /// Reference to texture + public static CTextureRef EnqueueTexture(String texturePath) + { + return _Draw.EnqueueTexture(texturePath); + } + /// /// Requests updating a texture from a bitmap
/// Use this if you add textures from another thread or don't need it immediatelly diff --git a/Vocaluxe/Lib/Draw/CDirect3D.cs b/Vocaluxe/Lib/Draw/CDirect3D.cs index 0aa91787..430e4a54 100644 --- a/Vocaluxe/Lib/Draw/CDirect3D.cs +++ b/Vocaluxe/Lib/Draw/CDirect3D.cs @@ -42,21 +42,27 @@ namespace Vocaluxe.Lib.Draw } } - class CD3DTexture : CTextureBase, IDisposable + class CD3DTexture : CTextureBase { public readonly Texture D3DTexture; public CD3DTexture(Device device, Size dataSize, int texWidth = 0, int texHeight = 0) : base(dataSize, texWidth, texHeight) { - D3DTexture = new Texture(device, W2, H2, 0, Usage.AutoGenerateMipMap, Format.A8R8G8B8, Pool.Managed); + //Create a new texture in the managed pool, which does not need to be recreated on a lost device + //because a copy of the texture is hold in the Ram + D3DTexture = device == null ? null : new Texture(device, W2, H2, 0, Usage.AutoGenerateMipMap, Format.A8R8G8B8, Pool.Managed); } - public void Dispose() + public override bool IsLoaded { - if (D3DTexture.Disposed) - throw new ObjectDisposedException(GetType().Name); - D3DTexture.Dispose(); - RefCount = 0; + get { return D3DTexture != null; } + } + + public override void Dispose() + { + base.Dispose(); + if (D3DTexture != null) + D3DTexture.Dispose(); } } @@ -575,8 +581,8 @@ namespace Vocaluxe.Lib.Draw #region Textures protected override CD3DTexture _CreateTexture(Size dataSize) { - //Create a new texture in the managed pool, which does not need to be recreated on a lost device - //because a copy of the texture is hold in the Ram + if (dataSize.Width < 0) + return new CD3DTexture(null, dataSize); return new CD3DTexture(_Device, dataSize, _CheckForNextPowerOf2(dataSize.Width), _CheckForNextPowerOf2(dataSize.Height)); } diff --git a/Vocaluxe/Lib/Draw/CDrawBase.cs b/Vocaluxe/Lib/Draw/CDrawBase.cs index 1eca5564..06718c84 100644 --- a/Vocaluxe/Lib/Draw/CDrawBase.cs +++ b/Vocaluxe/Lib/Draw/CDrawBase.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; -using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; +using System.Threading.Tasks; using Vocaluxe.Base; using VocaluxeLib; using VocaluxeLib.Draw; @@ -12,40 +13,59 @@ using System.Diagnostics; namespace Vocaluxe.Lib.Draw { + /// + /// Base class for graphic drivers + /// A few notes: + /// Writes to textures (TTextureType) should only be done in the main thread + /// Writes to the RefCount should only be done in the main thread and guarded by _Textures lock + /// + /// abstract class CDrawBase where TTextureType : CTextureBase, IDisposable { private enum EQueueAction { Add, - Update + Update, + Delete } private struct STextureQueue { public readonly WeakReference TextureRef; + /// + /// Only valid if Data is byte[] + /// public readonly Size DataSize; - //Use either Data or DataBmp! - public readonly byte[] Data; - //This is disposed after use - public readonly Bitmap DataBmp; + public readonly object Data; public readonly EQueueAction Action; + /// + /// Creates a new Queue entry + /// + /// + /// + /// Texture data as a bitmap + public STextureQueue(CTextureRef textureRef, EQueueAction action, Bitmap data) + { + TextureRef = new WeakReference(textureRef); + Action = action; + Data = data; + DataSize = new Size(); + } + + /// + /// Creates a new Queue entry + /// + /// + /// + /// Size of data + /// Texture data public STextureQueue(CTextureRef textureRef, EQueueAction action, Size dataSize, byte[] data) { TextureRef = new WeakReference(textureRef); Action = action; DataSize = dataSize; Data = data; - DataBmp = null; - } - - public STextureQueue(CTextureRef textureRef, EQueueAction action, Bitmap bmp) - { - TextureRef = new WeakReference(textureRef); - Action = action; - DataSize = bmp.GetSize(); - Data = null; - DataBmp = bmp; } } @@ -66,14 +86,15 @@ namespace Vocaluxe.Lib.Draw protected readonly CKeys _Keys = new CKeys(); protected readonly CMouse _Mouse = new CMouse(); - private readonly Object _MutexID = new object(); private int _NextID; + private readonly Object _MutexID = new object(); // Maps texture IDs to textures. Multiple IDs can be mapped to one texture (-->texture.RefCount>1) private readonly Dictionary _Textures = new Dictionary(); - // Maps texturePaths to textures. Texture may have already been disposed (RefCount<=0) or even null + // Maps texturePaths to textures. Texture may have already been disposed (RefCount<=0) private readonly Dictionary _TextureCache = new Dictionary(); - private readonly Queue _TexturesToLoad = new Queue(); + private readonly Queue _TextureQueue = new Queue(); + private readonly Dictionary> _BitmapsLoading = new Dictionary>(); private int _TextureCount; protected int _H; @@ -87,6 +108,8 @@ namespace Vocaluxe.Lib.Draw protected int _BorderBottom; protected EGeneralAlignment _CurrentAlignment = EGeneralAlignment.Middle; + private int _MainThreadID; + protected abstract void _ClearScreen(); protected abstract void _AdjustNewBorders(); protected abstract void _LeaveFullScreen(); @@ -99,8 +122,23 @@ namespace Vocaluxe.Lib.Draw protected abstract void _EnterFullScreen(); + public virtual bool Init() + { + _MainThreadID = Thread.CurrentThread.ManagedThreadId; + return true; + } + + /// + /// This makes sure, the function is called from within the main thread + /// + private void _EnsureMainThread() + { + Debug.Assert(_MainThreadID == Thread.CurrentThread.ManagedThreadId); + } + public virtual void Unload() { + _EnsureMainThread(); lock (_Textures) { //Dispose all textures @@ -196,6 +234,18 @@ namespace Vocaluxe.Lib.Draw } } + /// + /// Returns true if a texture with the given size needs to be resized + /// + /// + /// + private static bool _RequiresResize(Size size) + { + int maxArea = _GetMaxTextureArea(); + int curArea = size.Width * size.Height; + return curArea > maxArea; + } + /// /// Calculates the size for a new texture using the quality and smart values for rescaling /// @@ -207,6 +257,7 @@ namespace Vocaluxe.Lib.Draw int curArea = size.Width * size.Height; if (curArea <= maxArea) return size; + Debug.Assert(_RequiresResize(size)); double factor = Math.Sqrt((double)maxArea / curArea); Size newSize = new Size((int)(size.Width * factor), (int)(size.Height * factor)); if (!_NonPowerOf2TextureSupported) @@ -240,7 +291,8 @@ namespace Vocaluxe.Lib.Draw } /// - /// Factory method to create a texture of the actual type + /// Factory method to create a texture of the actual type
+ /// The implementer has to create an invalid texture (IsLoaded=false) if dataSize=(-1,-1) ///
/// /// @@ -293,21 +345,36 @@ namespace Vocaluxe.Lib.Draw } } + /// + /// Adds the texture to the cache + /// + /// + /// + /// private void _AddToCache(TTextureType texture, Size origSize, string texturePath) { - if (texture != null && !String.IsNullOrEmpty(texturePath)) + if (String.IsNullOrEmpty(texturePath)) + return; + Debug.Assert(texture != null); + lock (_Textures) { + Debug.Assert(!_TextureCache.ContainsKey(texturePath)); texture.TexturePath = texturePath; STextureCacheEntry cacheEntry = new STextureCacheEntry {OrigSize = origSize, Texture = texture}; - lock (_Textures) - { - _TextureCache[texturePath] = cacheEntry; - } + _TextureCache.Add(texturePath, cacheEntry); } } + /// + /// Gets a new texture reference from the cache
+ /// Thread safe but you should hold the _TextureCache lock if you need the loader task + ///
+ /// + /// private CTextureRef _GetFromCache(string texturePath) { + if (String.IsNullOrEmpty(texturePath)) + return null; lock (_Textures) { STextureCacheEntry cacheEntry; @@ -323,8 +390,9 @@ namespace Vocaluxe.Lib.Draw } /// - /// Method used to add a texture to the texture list and return a TextureRef to it
- /// Important: This should be the only function to ever write to _Textures + /// Method used to add a texture to the texture list and return a TextureRef to it
+ /// If you are using an already used texture, you have to hold the _Textures lock or call from the main thread!
+ /// Thread safe ///
/// /// @@ -332,25 +400,26 @@ namespace Vocaluxe.Lib.Draw protected CTextureRef _GetTextureReference(Size origSize, TTextureType texture) { Debug.Assert(origSize.Width > 0 && origSize.Height > 0); + Debug.Assert(texture != null); int id; lock (_MutexID) { id = _NextID++; } CTextureRef textureRef = new CTextureRef(id, origSize); - if (texture != null) - texture.RefCount++; - if (texture == null || texture.RefCount == 1) - _TextureCount++; lock (_Textures) { + if (texture.RefCount == 0) + _TextureCount++; + texture.RefCount++; _Textures.Add(id, texture); } return textureRef; } /// - /// Method used to add a texture to the texture list and return a TextureRef to it
+ /// Method used to add a texture to the texture list and return a TextureRef to it
+ /// Thread safe
/// Convenience method ///
/// @@ -376,7 +445,7 @@ namespace Vocaluxe.Lib.Draw return true; } - protected bool _GetTexture(CTextureRef textureRef, out TTextureType texture) + protected bool _GetTexture(CTextureRef textureRef, out TTextureType texture, bool checkDrawable = true) { if (textureRef == null) { @@ -385,7 +454,7 @@ namespace Vocaluxe.Lib.Draw } lock (_Textures) { - return _Textures.TryGetValue(textureRef.ID, out texture) && texture != null; + return _Textures.TryGetValue(textureRef.ID, out texture) && (!checkDrawable || texture.IsLoaded); } } @@ -395,23 +464,27 @@ namespace Vocaluxe.Lib.Draw /// private void _DisposeTexture(TTextureType texture) { - if (texture != null && --texture.RefCount <= 0) + _EnsureMainThread(); + lock (_Textures) { - if (texture.TexturePath != null) - _TextureCache.Remove(texture.TexturePath); - texture.Dispose(); - _TextureCount--; + if (texture != null && --texture.RefCount <= 0) + { + if (texture.TexturePath != null) + _TextureCache.Remove(texture.TexturePath); + texture.Dispose(); + _TextureCount--; + } } } private void _CheckQueue() { - lock (_Textures) + _EnsureMainThread(); + lock (_TextureQueue) { - while (_TexturesToLoad.Count > 0) + while (_TextureQueue.Count > 0) { - STextureQueue q = _TexturesToLoad.Dequeue(); - Debug.Assert(q.Data != null ^ q.DataBmp != null); + STextureQueue q = _TextureQueue.Dequeue(); CTextureRef textureRef = q.TextureRef.Target as CTextureRef; if (!q.TextureRef.IsAlive || textureRef == null || !_Textures.ContainsKey(textureRef.ID)) continue; @@ -419,23 +492,78 @@ namespace Vocaluxe.Lib.Draw if (q.Action == EQueueAction.Add) { TTextureType texture = null; - if (q.Data == null) - _WriteBitmapToTexture(ref texture, q.DataBmp); + // ReSharper disable CanBeReplacedWithTryCastAndCheckForNull + if (q.Data is Bitmap) + _WriteBitmapToTexture(ref texture, (Bitmap)q.Data); + else if (q.Data is byte[]) + texture = _CreateAndFillTexture(q.DataSize, (byte[])q.Data); else - texture = _CreateAndFillTexture(q.DataSize, q.Data); - texture.RefCount = 1; //Just created, so only 1 reference - _Textures[textureRef.ID] = texture; + throw new ArgumentException("q.Data is of invalid type"); + // ReSharper restore CanBeReplacedWithTryCastAndCheckForNull + TTextureType oldTexture = _Textures[textureRef.ID]; + _MergeTextures(oldTexture, texture); } - else + else if (q.Action == EQueueAction.Update) { - if (q.Data == null) - UpdateTexture(textureRef, q.DataBmp); + // ReSharper disable CanBeReplacedWithTryCastAndCheckForNull + if (q.Data is Bitmap) + UpdateTexture(textureRef, (Bitmap)q.Data); + else if (q.Data is byte[]) + UpdateTexture(textureRef, q.DataSize.Width, q.DataSize.Height, (byte[])q.Data); else - UpdateTexture(textureRef, q.DataSize.Width, q.DataSize.Height, q.Data); + throw new ArgumentException("q.Data is of invalid type"); + // ReSharper restore CanBeReplacedWithTryCastAndCheckForNull } + else if (q.Action == EQueueAction.Delete) + RemoveTexture(ref textureRef); } } } + + /// + /// Replaces one oldTexture by newTexture summing the refCounts. Also disposes oldTexture + /// IMPORTANT: Caller MUST hold the _Textures lock! + /// + /// + /// + private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture) + { + Debug.Assert(oldTexture.RefCount > 0); + newTexture.RefCount += oldTexture.RefCount; + // ReSharper disable AccessToDisposedClosure + IEnumerable oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray(); + // ReSharper restore AccessToDisposedClosure + foreach (int key in oldKeys) + _Textures[key] = newTexture; + oldTexture.Dispose(); + } + + /// + /// Checks if a texture is currently beeing loaded and waits for completion if it is
+ /// Required for cached entries, but should not be called from inside any locks unless async is true + ///
+ /// + /// + /// If true, do not block + private void _WaitForTextureLoaded(CTextureRef textureRef, string filePath, bool async) + { + // Check if texture is already loaded + if (textureRef.OrigSize.Width > 0) + return; + Task loader; + lock (_BitmapsLoading) + { + if (!_BitmapsLoading.TryGetValue(filePath, out loader)) + Debug.Assert(false, "Corruption: Texture not loaded but no loader found"); + } + // We found the texture in the cache, but its bitmap is not yet loaded + // So wait for completion and update the origSize once it is available + // Note: loader.Result blocks till completion of the task + if (async) + Task.Factory.StartNew(() => textureRef.OrigSize = loader.Result); + else + textureRef.OrigSize = loader.Result; + } #endregion private/protected /// @@ -445,25 +573,22 @@ namespace Vocaluxe.Lib.Draw /// A STexture object containing the added texture public CTextureRef AddTexture(string texturePath) { - CTextureRef textureRef = _GetFromCache(texturePath); + _EnsureMainThread(); + CTextureRef textureRef; + lock (_TextureCache) + { + textureRef = _GetFromCache(texturePath); + } if (textureRef != null) + { + _WaitForTextureLoaded(textureRef, texturePath, false); + Debug.Assert(textureRef.OrigSize.Width > 0); return textureRef; + } - if (!File.Exists(texturePath)) - { - CLog.LogError("Can't find File: " + texturePath); + Bitmap bmp = CHelper.LoadBitmap(texturePath); + if (bmp == null) return null; - } - Bitmap bmp; - try - { - bmp = new Bitmap(texturePath); - } - catch (Exception) - { - CLog.LogError("Error loading Texture: " + texturePath); - return null; - } try { textureRef = AddTexture(bmp, texturePath); @@ -477,6 +602,7 @@ namespace Vocaluxe.Lib.Draw public CTextureRef AddTexture(Bitmap bmp) { + _EnsureMainThread(); return AddTexture(bmp, null); } @@ -485,52 +611,176 @@ namespace Vocaluxe.Lib.Draw /// /// The Bitmap of which the texure will be created from /// - /// A STexture object containing the added texture + /// A reference to the texture public CTextureRef AddTexture(Bitmap bmp, string texturePath) { + _EnsureMainThread(); if (bmp.Height == 0 || bmp.Width == 0) return null; + CTextureRef textureRef; Size origSize = bmp.GetSize(); - TTextureType texture = null; - _WriteBitmapToTexture(ref texture, bmp); + // Make the Get/Add Cache methods atomic + lock (_TextureCache) + { + textureRef = _GetFromCache(texturePath); + if (textureRef == null) + { + _WriteBitmapToTexture(ref texture, bmp); - _AddToCache(texture, origSize, texturePath); - return _GetTextureReference(origSize, texture); + _AddToCache(texture, origSize, texturePath); + } + } + if (textureRef == null) + textureRef = _GetTextureReference(origSize, texture); + else + _WaitForTextureLoaded(textureRef, texturePath, false); + Debug.Assert(textureRef.OrigSize.Width > 0); + return textureRef; } public CTextureRef AddTexture(int w, int h, byte[] data) { + _EnsureMainThread(); TTextureType texture = _CreateAndFillTexture(new Size(w, h), data); return _GetTextureReference(w, h, texture); } public CTextureRef EnqueueTexture(int w, int h, byte[] data) { - lock (_Textures) + lock (_TextureQueue) { - CTextureRef textureRef = _GetTextureReference(w, h, null); - _TexturesToLoad.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, new Size(w, h), data)); + CTextureRef textureRef = _GetTextureReference(w, h, _CreateTexture(new Size(-1, -1))); + _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, new Size(w, h), data)); return textureRef; } } public CTextureRef EnqueueTexture(Bitmap bmp) { - lock (_Textures) + CTextureRef textureRef = _GetTextureReference(bmp.GetSize(), _CreateTexture(new Size(-1, -1))); + if (_RequiresResize(bmp.GetSize())) { - CTextureRef textureRef = _GetTextureReference(bmp.GetSize(), null); - _TexturesToLoad.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp)); + Task.Factory.StartNew(() => + { + Bitmap bmp2 = bmp.Resize(_GetNewTextureSize(bmp.Size)); + bmp.Dispose(); + lock (_TextureQueue) + { + _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp2)); + } + }); + } + else + { + lock (_TextureQueue) + { + _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp)); + } + } + + return textureRef; + } + + public CTextureRef EnqueueTexture(String filePath) + { + // 3 important scenarios: (+ means after, || means in parallel) + // 1) EnqueueTexture(fileA) (+ or ||) EnqueueTexture(fileA) --> No block on 2nd call and same texture returned + // 2) EnqueueTexture(fileA) + AddTexture(fileA) before texture is loaded --> Wait in AddTexture for this to finish + // 3) a) AddTexture(fileA) || b) EnqueueTexture(fileA) with a)getFromCache + b)getFromCache + a)addToCache + b)addToCache --> Only keep one copy + // All solved with atomic get/add cache and _WaitForTextureLoaded + + CTextureRef textureRef; + TTextureType texture = null; + Task loader = null; + // Make Get/Add cache atomic + lock (_TextureCache) + { + textureRef = _GetFromCache(filePath); + if (textureRef == null) + { + Size invalidSize = new Size(-1, -1); + texture = _CreateTexture(invalidSize); + // This cache entry has to be updated once the real origSize is available + _AddToCache(texture, invalidSize, filePath); + textureRef = _GetTextureReference(invalidSize, texture); + // Add the loader to the dictionary in the same lock as the add cache call + // Avoids that we get a cache entry in AddTexture but no Task + loader = new Task(() => _LoadAndEnqueueBitmap(filePath, textureRef)); + lock (_BitmapsLoading) + { + _BitmapsLoading.Add(filePath, loader); + } + } + } + + if (texture == null) + { + // Found in cache + _WaitForTextureLoaded(textureRef, filePath, true); return textureRef; } + + Debug.Assert(loader != null); + Debug.Assert(texture != null); + loader.Start(); + return textureRef; + } + + /// + /// Helper function for loading a bitmap in a task + /// + /// + /// + /// + private Size _LoadAndEnqueueBitmap(string filePath, CTextureRef textureRef) + { + Size origSize; + Bitmap bmp = CHelper.LoadBitmap(filePath); + if (bmp == null) + { + RemoveTexture(ref textureRef); // Done asynchonously in the function + origSize = new Size(-1, -1); + } + else + { + origSize = bmp.GetSize(); + textureRef.OrigSize = origSize; + // Update cache, use the same lock as in add/get cache methods + lock (_Textures) + { + STextureCacheEntry cacheEntry; + if (_TextureCache.TryGetValue(filePath, out cacheEntry)) + { + cacheEntry.OrigSize = origSize; + _TextureCache[filePath] = cacheEntry; + } + } + Size size = _GetNewTextureSize(bmp.GetSize()); + if (!size.Equals(bmp.GetSize())) + { + Bitmap bmp2 = bmp.Resize(size); + bmp.Dispose(); + bmp = bmp2; + } + lock (_TextureQueue) + { + _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp)); + } + } + lock (_BitmapsLoading) + { + _BitmapsLoading.Remove(filePath); + } + return origSize; } public void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp) { - lock (_Textures) + lock (_TextureQueue) { - _TexturesToLoad.Enqueue(new STextureQueue(textureRef, EQueueAction.Update, bmp)); + _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Update, bmp)); } } @@ -544,11 +794,12 @@ namespace Vocaluxe.Lib.Draw /// True if succeeded public void UpdateTexture(CTextureRef textureRef, int w, int h, byte[] data) { + _EnsureMainThread(); TTextureType texture; - if (!_GetTexture(textureRef, out texture)) + if (!_GetTexture(textureRef, out texture, false)) return; bool reuseTexture = _IsTextureUsable(texture, w, h); - if (reuseTexture) + if (reuseTexture && texture.RefCount == 1) { texture.DataSize = new Size(w, h); _WriteDataToTexture(texture, data); @@ -558,20 +809,18 @@ namespace Vocaluxe.Lib.Draw _DisposeTexture(texture); texture = _CreateAndFillTexture(new Size(w, h), data); texture.RefCount = 1; - lock (_Textures) - { - _Textures[textureRef.ID] = texture; - } + _Textures[textureRef.ID] = texture; } } public void UpdateTexture(CTextureRef textureRef, Bitmap bmp) { + _EnsureMainThread(); TTextureType texture; - if (!_GetTexture(textureRef, out texture)) + if (!_GetTexture(textureRef, out texture, false)) return; bool reuseTexture = _IsTextureUsable(texture, bmp.Width, bmp.Height); - if (reuseTexture) + if (reuseTexture && texture.RefCount == 1) _WriteBitmapToTexture(ref texture, bmp); else { @@ -579,18 +828,19 @@ namespace Vocaluxe.Lib.Draw texture = null; _WriteBitmapToTexture(ref texture, bmp); texture.RefCount = 1; - lock (_Textures) - { - _Textures[textureRef.ID] = texture; - } + _Textures[textureRef.ID] = texture; } } public CTextureRef CopyTexture(CTextureRef textureRef) { + _EnsureMainThread(); TTextureType texture; - if (!_GetTexture(textureRef, out texture)) + if (!_GetTexture(textureRef, out texture, false)) return null; + // If bitmap is not yet loaded, wait for it + if (!String.IsNullOrEmpty(texture.TexturePath)) + _WaitForTextureLoaded(textureRef, texture.TexturePath, false); return _GetTextureReference(textureRef.OrigSize, texture); } @@ -602,6 +852,15 @@ namespace Vocaluxe.Lib.Draw { if (textureRef == null) return; + if (_MainThreadID != Thread.CurrentThread.ManagedThreadId) + { + lock (_TextureQueue) + { + _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Delete, null)); + } + textureRef = null; + return; + } lock (_Textures) { TTextureType t; @@ -621,11 +880,15 @@ namespace Vocaluxe.Lib.Draw } #endregion + // Resharper doesn't get that this is used -.- + // ReSharper disable UnusedMemberHiearchy.Global /// /// Starts the rendering /// public virtual void MainLoop() { + // ReSharper restore UnusedMemberHiearchy.Global + _EnsureMainThread(); _Run = true; _Fullscreen = false; diff --git a/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs b/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs index e387281b..8ac494e8 100644 --- a/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs +++ b/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs @@ -171,8 +171,10 @@ namespace Vocaluxe.Lib.Draw #endregion - public virtual bool Init() + public override bool Init() { + if (!base.Init()) + return false; _Form.Icon = new Icon(Path.Combine(CSettings.ProgramFolder, CSettings.FileNameIcon)); _Form.Text = CSettings.GetFullVersionText(); ((IFormHook)_Form).OnMessage = _OnMessageAvoidScreenOff; diff --git a/Vocaluxe/Lib/Draw/CDrawWinForm.cs b/Vocaluxe/Lib/Draw/CDrawWinForm.cs index 070f38b4..efec130f 100644 --- a/Vocaluxe/Lib/Draw/CDrawWinForm.cs +++ b/Vocaluxe/Lib/Draw/CDrawWinForm.cs @@ -380,6 +380,11 @@ namespace Vocaluxe.Lib.Draw return AddTexture(w, h, data); } + public CTextureRef EnqueueTexture(string texturePath) + { + return AddTexture(texturePath); + } + public CTextureRef EnqueueTexture(Bitmap bmp) { return AddTexture(bmp); diff --git a/Vocaluxe/Lib/Draw/COpenGL.cs b/Vocaluxe/Lib/Draw/COpenGL.cs index 997bd148..c66e442a 100644 --- a/Vocaluxe/Lib/Draw/COpenGL.cs +++ b/Vocaluxe/Lib/Draw/COpenGL.cs @@ -56,25 +56,38 @@ namespace Vocaluxe.Lib.Draw } } - class COGLTexture : CTextureBase, IDisposable + class COGLTexture : CTextureBase { //The texture "name" according to the specs public readonly int Name; - private bool _IsDisposed; - public COGLTexture(int name, Size dataSize, int texWidth = 0, int texHeight = 0) : base(dataSize, texWidth, texHeight) { Name = name; + if (name == 0) + return; + GL.BindTexture(TextureTarget.Texture2D, Name); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, W2, H2, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear); + + GL.BindTexture(TextureTarget.Texture2D, 0); } - public void Dispose() + public override bool IsLoaded { - if (_IsDisposed) - throw new ObjectDisposedException(GetType().Name); - _IsDisposed = true; - GL.DeleteTexture(Name); - RefCount = 0; + get { return Name != 0; } + } + + public override void Dispose() + { + base.Dispose(); + if (Name != 0) + GL.DeleteTexture(Name); } } @@ -409,17 +422,10 @@ namespace Vocaluxe.Lib.Draw #region Textures protected override COGLTexture _CreateTexture(Size dataSize) { + if (dataSize.Width < 0) + return new COGLTexture(0, dataSize); COGLTexture texture = new COGLTexture(GL.GenTexture(), dataSize, _CheckForNextPowerOf2(dataSize.Width), _CheckForNextPowerOf2(dataSize.Height)); - GL.BindTexture(TextureTarget.Texture2D, texture.Name); - GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, texture.W2, texture.H2, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear); - - GL.BindTexture(TextureTarget.Texture2D, 0); return texture; } diff --git a/Vocaluxe/Lib/Draw/CTextureBase.cs b/Vocaluxe/Lib/Draw/CTextureBase.cs index 2d79345e..eb9d11bf 100644 --- a/Vocaluxe/Lib/Draw/CTextureBase.cs +++ b/Vocaluxe/Lib/Draw/CTextureBase.cs @@ -1,8 +1,9 @@ -using System.Drawing; +using System; +using System.Drawing; namespace Vocaluxe.Lib.Draw { - abstract class CTextureBase + abstract class CTextureBase : IDisposable { public int RefCount; public string TexturePath; @@ -21,82 +22,75 @@ namespace Vocaluxe.Lib.Draw } } - private int _W2, _H2; /// - /// Internal texture width (on device), a power of 2 if necessary + /// texture width (on device), a power of 2 if necessary /// - public int W2 - { - get { return _W2; } - set - { - _W2 = value; - _CalcRatios(); - } - } + public int W2 { get; protected set; } /// - /// Internal texture height (on device), a power of 2 if necessary + /// texture height (on device), a power of 2 if necessary /// - public int H2 - { - get { return _H2; } - set - { - _H2 = value; - _CalcRatios(); - } - } + public int H2 { get; protected set; } /// - /// Internal use. Specifies which part of texture memory is actually used + /// Specifies which part of texture memory is actually used /// public float WidthRatio { get; private set; } /// - /// Internal use. Specifies which part of texture memory is actually used + /// Specifies which part of texture memory is actually used /// public float HeightRatio { get; private set; } /// - /// Creates a new texture reference + /// True if the texture contains usefull data + /// + public abstract bool IsLoaded { get; } + + /// + /// Creates a new texture /// /// Size of the data used /// Width in video memory /// Height in video memory - public CTextureBase(Size dataSize, int texWidth = 0, int texHeight = 0) + protected CTextureBase(Size dataSize, int texWidth = 0, int texHeight = 0) { _DataSize = dataSize; - _W2 = (texWidth > 0) ? texWidth : dataSize.Width; - _H2 = (texHeight > 0) ? texHeight : dataSize.Height; + W2 = (texWidth > 0) ? texWidth : dataSize.Width; + H2 = (texHeight > 0) ? texHeight : dataSize.Height; _CalcRatios(); } - public bool IsValid() - { - return RefCount > 0; - } - private void _CalcRatios() { // OpenGL has a problem with partial (NPOT) textures, so we remove 1 pixel - if (_DataSize.Width == _W2) + if (_DataSize.Width == W2) WidthRatio = 1f; else { int mod = 0; if (_DataSize.Width > 1) mod = -1; - WidthRatio = (float)(_DataSize.Width + mod) / _W2; + WidthRatio = (float)(_DataSize.Width + mod) / W2; } - if (_DataSize.Height == _H2) + if (_DataSize.Height == H2) HeightRatio = 1f; else { int mod = 0; if (_DataSize.Height > 1) mod = -1; - HeightRatio = (float)(_DataSize.Height + mod) / _H2; + HeightRatio = (float)(_DataSize.Height + mod) / H2; } } + + private bool _IsDisposed; + + public virtual void Dispose() + { + if (_IsDisposed) + throw new ObjectDisposedException(GetType().Name); + RefCount = 0; + _IsDisposed = true; + } } -} +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Draw/IDraw.cs b/Vocaluxe/Lib/Draw/IDraw.cs index 8a912cc2..7b4b37ad 100644 --- a/Vocaluxe/Lib/Draw/IDraw.cs +++ b/Vocaluxe/Lib/Draw/IDraw.cs @@ -39,11 +39,12 @@ namespace Vocaluxe.Lib.Draw CTextureRef AddTexture(string texturePath); CTextureRef AddTexture(Bitmap bitmap); CTextureRef AddTexture(int w, int h, byte[] data); - CTextureRef EnqueueTexture(int w, int h, byte[] data); - CTextureRef EnqueueTexture(Bitmap bmp); - void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp); - void UpdateTexture(CTextureRef texture, int w, int h, byte[] data); void UpdateTexture(CTextureRef texture, Bitmap bmp); + void UpdateTexture(CTextureRef texture, int w, int h, byte[] data); + CTextureRef EnqueueTexture(string texturePath); + CTextureRef EnqueueTexture(Bitmap bmp); + CTextureRef EnqueueTexture(int w, int h, byte[] data); + void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp); CTextureRef CopyTexture(CTextureRef textureRef); void RemoveTexture(ref CTextureRef texture); diff --git a/VocaluxeLib/CEncoding.cs b/VocaluxeLib/CEncoding.cs new file mode 100644 index 00000000..7b763d5b --- /dev/null +++ b/VocaluxeLib/CEncoding.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace VocaluxeLib +{ + static class CEncoding + { + public static Encoding GetEncoding(this string encodingName) + { + switch (encodingName) + { + case "AUTO": + return Encoding.Default; + case "CP1250": + return Encoding.GetEncoding(1250); + case "CP1252": + return Encoding.GetEncoding(1252); + case "LOCALE": + return Encoding.Default; + case "UTF8": + return Encoding.UTF8; + default: + return Encoding.Default; + } + } + + public static string GetEncodingName(this Encoding enc) + { + string result = "UTF8"; + + if (enc.CodePage == 1250) + result = "CP1250"; + + if (enc.CodePage == 1252) + result = "CP1252"; + + return result; + } + } +} \ No newline at end of file diff --git a/VocaluxeLib/CHelper.cs b/VocaluxeLib/CHelper.cs index b3392fe4..ea314518 100644 --- a/VocaluxeLib/CHelper.cs +++ b/VocaluxeLib/CHelper.cs @@ -21,7 +21,6 @@ using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; -using System.Text; namespace VocaluxeLib { @@ -302,40 +301,30 @@ namespace VocaluxeLib filename = Path.Combine(path, filename); return filename + ext; } - } - static class CEncoding - { - public static Encoding GetEncoding(this string encodingName) + /// + /// Loads a bitmap from a file logging errors + /// + /// Full path to image file + /// Bitmap or null on error + public static Bitmap LoadBitmap(string filePath) { - switch (encodingName) + if (!File.Exists(filePath)) { - case "AUTO": - return Encoding.Default; - case "CP1250": - return Encoding.GetEncoding(1250); - case "CP1252": - return Encoding.GetEncoding(1252); - case "LOCALE": - return Encoding.Default; - case "UTF8": - return Encoding.UTF8; - default: - return Encoding.Default; + CBase.Log.LogError("Can't find File: " + filePath); + return null; } - } - - public static string GetEncodingName(this Encoding enc) - { - string result = "UTF8"; - - if (enc.CodePage == 1250) - result = "CP1250"; - - if (enc.CodePage == 1252) - result = "CP1252"; - - return result; + Bitmap bmp; + try + { + bmp = new Bitmap(filePath); + } + catch (Exception) + { + CBase.Log.LogError("Error loading bitmap: " + filePath); + return null; + } + return bmp; } } } \ No newline at end of file diff --git a/VocaluxeLib/VocaluxeLib.csproj b/VocaluxeLib/VocaluxeLib.csproj index 2ac08aae..11f27989 100644 --- a/VocaluxeLib/VocaluxeLib.csproj +++ b/VocaluxeLib/VocaluxeLib.csproj @@ -76,6 +76,7 @@ +