diff --git a/Vocaluxe/Lib/Draw/CDrawBase.cs b/Vocaluxe/Lib/Draw/CDrawBase.cs
index d44322be..3ce66020 100644
--- a/Vocaluxe/Lib/Draw/CDrawBase.cs
+++ b/Vocaluxe/Lib/Draw/CDrawBase.cs
@@ -1,11 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.Linq;
-using System.Runtime.InteropServices;
using System.Threading;
-using System.Threading.Tasks;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -20,65 +14,8 @@ namespace Vocaluxe.Lib.Draw
/// Writes to the RefCount of existing textures should only be done in the main thread and guarded by _Textures lock
///
///
- abstract class CDrawBase where TTextureType : CTextureBase, IDisposable
+ abstract class CDrawBase : CTextureProvider where TTextureType : CTextureBase, IDisposable
{
- private enum EQueueAction
- {
- Add,
- Update,
- Delete
- }
-
- private struct STextureQueue
- {
- public readonly object TextureOrRef;
- ///
- /// Only valid if Data is byte[]
- ///
- public readonly Size DataSize;
- public readonly object Data;
- public readonly EQueueAction Action;
-
- ///
- /// Creates a new Queue entry
- ///
- /// TTextureType or CTextureRef
- ///
- /// Texture data as a bitmap
- public STextureQueue(object textureOrRef, EQueueAction action, Bitmap data)
- {
- TextureOrRef = textureOrRef;
- Action = action;
- Data = data;
- DataSize = new Size();
- }
-
- ///
- /// Creates a new Queue entry
- ///
- /// TTextureType or CTextureRef
- ///
- /// Size of data
- /// Texture data
- public STextureQueue(object textureOrRef, EQueueAction action, Size dataSize, byte[] data)
- {
- TextureOrRef = textureOrRef;
- Action = action;
- DataSize = dataSize;
- Data = data;
- }
- }
-
- private struct STextureCacheEntry
- {
- public TTextureType Texture;
- ///
- /// Size of original image (e.g. of bmp)
- ///
- public Size OrigSize;
- }
-
- protected bool _NonPowerOf2TextureSupported;
protected bool _Fullscreen;
protected bool _Run;
@@ -86,17 +23,6 @@ namespace Vocaluxe.Lib.Draw
protected readonly CKeys _Keys = new CKeys();
protected readonly CMouse _Mouse = new CMouse();
- 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)
- private readonly Dictionary _TextureCache = new Dictionary();
- private readonly Queue _TextureQueue = new Queue();
- private readonly Dictionary> _BitmapsLoading = new Dictionary>();
- private int _TextureCount;
-
protected int _H;
protected int _W;
protected int _Y;
@@ -108,8 +34,6 @@ 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();
@@ -122,36 +46,6 @@ 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
- foreach (TTextureType texture in _Textures.Values)
- {
- if (texture != null && --texture.RefCount <= 0)
- texture.Dispose();
- }
- _Textures.Clear();
- _TextureCache.Clear();
- }
- }
-
protected void _AdjustAspect(bool reverse)
{
if (_W / (float)_H > CSettings.GetRenderAspect())
@@ -196,23 +90,6 @@ namespace Vocaluxe.Lib.Draw
}
}
- ///
- /// Calculates the next power of two if the device has the POW2 flag set
- ///
- /// The value of which the next power of two will be calculated
- /// The next power of two
- protected int _CheckForNextPowerOf2(int n)
- {
- if (_NonPowerOf2TextureSupported)
- return n;
- if (n < 0)
- throw new ArgumentOutOfRangeException("n", "Must be positive.");
- return (int)Math.Pow(2, Math.Ceiling(Math.Log(n, 2)));
- }
-
- #region Textures
-
- #region drawing
///
/// Struct that contains texture and world coordinates for drawing
///
@@ -414,739 +291,6 @@ namespace Vocaluxe.Lib.Draw
if (dc.Ty2 < dc.Ty1) // Make sure we actually draw something
_DrawTexture(texture, dc, color, true);
}
- #endregion drawing
-
- #region private/protected
- ///
- /// Returns the maximum area allowed for a texture based on the current quality
- ///
- ///
- private static int _GetMaxTextureArea()
- {
- switch (CConfig.TextureQuality)
- {
- case ETextureQuality.TR_CONFIG_TEXTURE_LOWEST:
- return 128 * 128;
- case ETextureQuality.TR_CONFIG_TEXTURE_LOW:
- return 256 * 128;
- case ETextureQuality.TR_CONFIG_TEXTURE_MEDIUM:
- return 512 * 256;
- case ETextureQuality.TR_CONFIG_TEXTURE_HIGH:
- return 1024 * 512;
- case ETextureQuality.TR_CONFIG_TEXTURE_HIGHEST:
- return CSettings.RenderW * CSettings.RenderH;
- default:
- throw new ArgumentException();
- }
- }
-
- ///
- /// 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
- ///
- /// New size for the texture
- private Size _GetNewTextureSize(Size size)
- {
- Debug.Assert(size.Width > 0 && size.Height > 0);
- int maxArea = _GetMaxTextureArea();
- 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)
- {
- if (size.Width < size.Height)
- {
- newSize.Width = _CheckForNextPowerOf2(newSize.Width);
- newSize.Height = _CheckForNextPowerOf2(maxArea / newSize.Width);
- if (newSize.Width * newSize.Height > maxArea)
- newSize.Height /= 2;
- }
- else
- {
- newSize.Height = _CheckForNextPowerOf2(newSize.Height);
- newSize.Width = _CheckForNextPowerOf2(maxArea / newSize.Height);
- if (newSize.Width * newSize.Height > maxArea)
- newSize.Width /= 2;
- }
- }
- Debug.Assert(newSize.Width * newSize.Height <= maxArea);
- return newSize;
- }
-
- protected abstract void _WriteDataToTexture(TTextureType texture, byte[] data);
-
- protected virtual void _WriteDataToTexture(TTextureType texture, IntPtr data)
- {
- byte[] dataArray = new byte[4 * texture.DataSize.Width * texture.DataSize.Height];
- Marshal.Copy(data, dataArray, 0, dataArray.Length);
- _WriteDataToTexture(texture, dataArray);
- }
-
- ///
- /// Factory method to create a texture of the actual type
- /// The implementer has to create an invalid texture (IsLoaded=false) if dataSize=(-1,-1)
- ///
- ///
- ///
- protected abstract TTextureType _CreateTexture(Size dataSize);
-
- ///
- /// Creates the texture specified by the reference and fills it with the given data
- ///
- ///
- ///
- ///
- private TTextureType _CreateAndFillTexture(Size dataSize, byte[] data)
- {
- TTextureType texture = _CreateTexture(dataSize);
- _WriteDataToTexture(texture, data);
- return texture;
- }
-
- ///
- /// Writes the specified bitmap to the texture, creating it if null
- /// Also does the resizing if required
- ///
- ///
- ///
- private void _WriteBitmapToTexture(ref TTextureType texture, Bitmap bmp)
- {
- Bitmap bmp2 = null;
- try
- {
- Size size = _GetNewTextureSize(bmp.GetSize());
- if (!size.Equals(bmp.GetSize()))
- {
- bmp2 = bmp.Resize(size);
- bmp = bmp2;
- }
-
- //Fill the new Bitmap with the texture data
- BitmapData bmpData = bmp.LockBits(bmp.GetRect(), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
- if (texture == null)
- texture = _CreateTexture(size);
- else
- texture.DataSize = size;
- _WriteDataToTexture(texture, bmpData.Scan0);
- bmp.UnlockBits(bmpData);
- }
- finally
- {
- if (bmp2 != null)
- bmp2.Dispose();
- }
- }
-
- ///
- /// Adds the texture to the cache
- /// Asserts that the cache entry does not exist yet. So make Get/Add cache atomic through use of _TextureCache lock!
- /// Thread safe
- ///
- ///
- ///
- ///
- private void _AddToCache(TTextureType texture, Size origSize, string 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};
- _TextureCache.Add(texturePath, cacheEntry);
- }
- }
-
- ///
- /// Gets a new texture reference from the cache
- /// Thread safe
- ///
- ///
- /// Task that loads the texture or null if not loading
- ///
- private CTextureRef _GetFromCache(string texturePath, out Task loader)
- {
- loader = null;
- if (String.IsNullOrEmpty(texturePath))
- return null;
- lock (_TextureCache)
- lock (_Textures)
- {
- STextureCacheEntry cacheEntry;
- if (_TextureCache.TryGetValue(texturePath, out cacheEntry))
- {
- if (cacheEntry.Texture.RefCount <= 0)
- _TextureCache.Remove(texturePath);
- else
- {
- if (cacheEntry.OrigSize.Width < 0)
- {
- lock (_BitmapsLoading)
- {
- loader = _BitmapsLoading[texturePath];
- }
- }
- return _GetTextureReference(cacheEntry.OrigSize, cacheEntry.Texture);
- }
- }
- }
- return null;
- }
-
- ///
- /// 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
- ///
- ///
- ///
- ///
- 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);
- 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
- /// Thread safe
- /// Convenience method
- ///
- ///
- ///
- ///
- ///
- protected CTextureRef _GetTextureReference(int origWidth, int origHeight, TTextureType texture)
- {
- return _GetTextureReference(new Size(origWidth, origHeight), texture);
- }
-
- private static bool _IsTextureUsable(TTextureType texture, int dataWidth, int dataHeight)
- {
- if (texture == null)
- return false;
- if (texture.DataSize.Width != dataWidth || texture.DataSize.Height != dataHeight)
- {
- if (texture.W2 > dataWidth || texture.H2 > dataHeight)
- return false; // Texture memory to small
- if (texture.W2 * 0.9 < dataWidth || texture.H2 * 0.9 < dataHeight)
- return false; // Texture memory to big
- }
- return true;
- }
-
- protected bool _GetTexture(CTextureRef textureRef, out TTextureType texture, bool checkDrawable = true)
- {
- if (textureRef == null)
- {
- texture = null;
- return false;
- }
- lock (_Textures)
- {
- return _Textures.TryGetValue(textureRef.ID, out texture) && (!checkDrawable || texture.IsLoaded);
- }
- }
-
- ///
- /// Decreases the RefCount of a texture and disposes it if necessary
- ///
- ///
- private void _DisposeTexture(TTextureType texture)
- {
- _EnsureMainThread();
- lock (_Textures)
- {
- if (texture != null && --texture.RefCount <= 0)
- {
- if (texture.TexturePath != null)
- _TextureCache.Remove(texture.TexturePath);
- texture.Dispose();
- _TextureCount--;
- }
- }
- }
-
- private void _CheckQueue()
- {
- _EnsureMainThread();
- lock (_TextureQueue)
- {
- while (_TextureQueue.Count > 0)
- {
- STextureQueue q = _TextureQueue.Dequeue();
-
- if (q.Action == EQueueAction.Add)
- {
- TTextureType oldTexture = q.TextureOrRef as TTextureType;
- Debug.Assert(oldTexture != null, "Queued type is wrong");
- Debug.Assert(!oldTexture.IsLoaded);
- if (oldTexture.RefCount <= 0)
- {
- Bitmap bmp = q.Data as Bitmap;
- if (bmp != null)
- bmp.Dispose();
- continue;
- }
- TTextureType texture = null;
- // ReSharper disable CanBeReplacedWithTryCastAndCheckForNull
- if (q.Data is Bitmap)
- {
- Bitmap bmp = (Bitmap)q.Data;
- _WriteBitmapToTexture(ref texture, bmp);
- bmp.Dispose();
- }
- else if (q.Data is byte[])
- texture = _CreateAndFillTexture(q.DataSize, (byte[])q.Data);
- else
- throw new ArgumentException("q.Data is of invalid type");
- // ReSharper restore CanBeReplacedWithTryCastAndCheckForNull
- _MergeTextures(oldTexture, texture);
- }
- else
- {
- CTextureRef textureRef = q.TextureOrRef as CTextureRef;
- Debug.Assert(textureRef != null, "Queued type is wrong");
- if (!_Textures.ContainsKey(textureRef.ID))
- {
- Bitmap bmp = q.Data as Bitmap;
- if (bmp != null)
- bmp.Dispose();
- continue;
- }
- if (q.Action == EQueueAction.Update)
- {
- // ReSharper disable CanBeReplacedWithTryCastAndCheckForNull
- if (q.Data is Bitmap)
- {
- Bitmap bmp = (Bitmap)q.Data;
- UpdateTexture(textureRef, bmp);
- bmp.Dispose();
- }
- else if (q.Data is byte[])
- UpdateTexture(textureRef, q.DataSize.Width, q.DataSize.Height, (byte[])q.Data);
- else
- 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
- ///
- ///
- ///
- private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture)
- {
- _EnsureMainThread();
- Debug.Assert(oldTexture.RefCount > 0);
- lock (_Textures)
- {
- newTexture.RefCount += oldTexture.RefCount;
- IEnumerable oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray();
- foreach (int key in oldKeys)
- _Textures[key] = newTexture;
- if (!String.IsNullOrEmpty(oldTexture.TexturePath))
- {
- newTexture.TexturePath = oldTexture.TexturePath;
- STextureCacheEntry cacheEntry;
- if (_TextureCache.TryGetValue(newTexture.TexturePath, out cacheEntry))
- {
- cacheEntry.Texture = newTexture;
- _TextureCache[newTexture.TexturePath] = cacheEntry;
- }
- }
- oldTexture.Dispose();
- }
- }
- #endregion private/protected
-
- ///
- /// Adds a texture and stores it in the VRam
- ///
- /// The texture's filepath
- /// A STexture object containing the added texture
- public CTextureRef AddTexture(string texturePath)
- {
- _EnsureMainThread();
- CTextureRef textureRef;
- Task loader;
- lock (_TextureCache)
- {
- textureRef = _GetFromCache(texturePath, out loader);
- }
- if (textureRef != null)
- {
- if (loader != null)
- textureRef.OrigSize = loader.Result;
- Debug.Assert(textureRef.OrigSize.Width > 0);
- return textureRef;
- }
-
- Bitmap bmp = CHelper.LoadBitmap(texturePath);
- if (bmp == null)
- return null;
- try
- {
- textureRef = AddTexture(bmp, texturePath);
- }
- finally
- {
- bmp.Dispose();
- }
- return textureRef;
- }
-
- public CTextureRef AddTexture(Bitmap bmp)
- {
- _EnsureMainThread();
- return AddTexture(bmp, null);
- }
-
- ///
- /// Adds a texture and stores it in the Vram
- ///
- /// The Bitmap of which the texure will be created from
- ///
- /// 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;
- Task loader;
- // Make the Get/Add Cache methods atomic
- lock (_TextureCache)
- {
- textureRef = _GetFromCache(texturePath, out loader);
- if (textureRef == null)
- {
- _WriteBitmapToTexture(ref texture, bmp);
-
- _AddToCache(texture, origSize, texturePath);
- }
- }
- if (textureRef == null)
- textureRef = _GetTextureReference(origSize, texture);
- else if (loader != null)
- textureRef.OrigSize = loader.Result;
- 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 (_TextureQueue)
- {
- TTextureType texture = _CreateTexture(new Size(-1, -1));
- CTextureRef textureRef = _GetTextureReference(w, h, texture);
- _TextureQueue.Enqueue(new STextureQueue(texture, EQueueAction.Add, new Size(w, h), data));
- return textureRef;
- }
- }
-
- ///
- /// Enqueues a bitmap to add or update the texture
- ///
- /// TTextureType (Add) or CTextureRef(Update)
- ///
- ///
- /// True if resizing should be done in an extra thread
- private void _EnqueueTextureAddOrUpdate(Object texture, Bitmap bmp, EQueueAction action, bool asyncResize)
- {
- Debug.Assert(action == EQueueAction.Add || action == EQueueAction.Update);
- Debug.Assert(action != EQueueAction.Add || texture is TTextureType);
- Debug.Assert(action != EQueueAction.Update || texture is CTextureRef);
- if (_RequiresResize(bmp.GetSize()))
- {
- if (asyncResize)
- Task.Factory.StartNew(() => _ResizeTextureAndEnqueue(texture, bmp, action));
- else
- _ResizeTextureAndEnqueue(texture, bmp, action);
- }
- else
- {
- lock (_TextureQueue)
- {
- _TextureQueue.Enqueue(new STextureQueue(texture, action, bmp));
- }
- }
- }
-
- ///
- /// Helper function to resize a bitmap and enqueue it
- ///
- ///
- ///
- ///
- private void _ResizeTextureAndEnqueue(object texture, Bitmap bmp, EQueueAction action)
- {
- Bitmap bmp2 = bmp.Resize(_GetNewTextureSize(bmp.Size));
- bmp.Dispose();
- lock (_TextureQueue)
- {
- _TextureQueue.Enqueue(new STextureQueue(texture, action, bmp2));
- }
- }
-
- public CTextureRef EnqueueTexture(Bitmap bmp)
- {
- TTextureType texture = _CreateTexture(new Size(-1, -1));
- CTextureRef textureRef = _GetTextureReference(bmp.GetSize(), texture);
- _EnqueueTextureAddOrUpdate(texture, bmp, EQueueAction.Add, true);
-
- 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;
- // Make Get/Add cache atomic
- lock (_TextureCache)
- {
- textureRef = _GetFromCache(filePath, out loader);
- if (textureRef == null)
- {
- Debug.Assert(loader == 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, texture));
- lock (_BitmapsLoading)
- {
- _BitmapsLoading.Add(filePath, loader);
- }
- }
- }
-
- if (texture == null)
- {
- // Found in cache
- CTextureRef tmp = textureRef; // Workaround for implicitly captured closure warning (false positive)
- if (loader != null)
- Task.Factory.StartNew(() => tmp.OrigSize = loader.Result);
- 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, TTextureType texture)
- {
- Bitmap bmp = CHelper.LoadBitmap(filePath);
- if (bmp == null)
- {
- RemoveTexture(ref textureRef); // Done asynchonously in the function
- lock (_Textures)
- {
- _TextureCache.Remove(filePath);
- lock (_BitmapsLoading)
- {
- _BitmapsLoading.Remove(filePath);
- }
- }
- return new Size(-1, -1);
- }
- Size 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;
- }
- lock (_BitmapsLoading)
- {
- _BitmapsLoading.Remove(filePath);
- }
- }
- _EnqueueTextureAddOrUpdate(texture, bmp, EQueueAction.Add, false);
- return origSize;
- }
-
- public void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp)
- {
- _EnqueueTextureAddOrUpdate(textureRef, bmp, EQueueAction.Update, true);
- }
-
- ///
- /// Updates the data of a texture
- ///
- /// The texture to update
- ///
- ///
- /// A byte array containing the new texture's data
- /// True if succeeded
- public void UpdateTexture(CTextureRef textureRef, int w, int h, byte[] data)
- {
- _EnsureMainThread();
- TTextureType texture;
- if (!_GetTexture(textureRef, out texture, false))
- return;
- bool reuseTexture = _IsTextureUsable(texture, w, h);
- if (reuseTexture && texture.RefCount == 1)
- {
- texture.DataSize = new Size(w, h);
- _WriteDataToTexture(texture, data);
- }
- else
- {
- _DisposeTexture(texture);
- texture = _CreateAndFillTexture(new Size(w, h), data);
- texture.RefCount = 1;
- _Textures[textureRef.ID] = texture;
- }
- }
-
- public void UpdateTexture(CTextureRef textureRef, Bitmap bmp)
- {
- _EnsureMainThread();
- TTextureType texture;
- if (!_GetTexture(textureRef, out texture, false))
- return;
- bool reuseTexture = _IsTextureUsable(texture, bmp.Width, bmp.Height);
- if (reuseTexture && texture.RefCount == 1)
- _WriteBitmapToTexture(ref texture, bmp);
- else
- {
- _DisposeTexture(texture);
- texture = null;
- _WriteBitmapToTexture(ref texture, bmp);
- texture.RefCount = 1;
- _Textures[textureRef.ID] = texture;
- }
- }
-
- public CTextureRef CopyTexture(CTextureRef textureRef)
- {
- _EnsureMainThread();
- TTextureType texture;
- if (!_GetTexture(textureRef, out texture, false))
- return null;
- // If bitmap is not yet loaded, wait for it
- if (!String.IsNullOrEmpty(texture.TexturePath))
- {
- Task loader;
- lock (_BitmapsLoading)
- {
- _BitmapsLoading.TryGetValue(texture.TexturePath, out loader);
- }
- if (loader != null)
- textureRef.OrigSize = loader.Result;
- }
- Debug.Assert(textureRef.OrigSize.Width > 0);
- return _GetTextureReference(textureRef.OrigSize, texture);
- }
-
- ///
- /// Removes a texture from the Vram
- ///
- /// The texture to be removed
- public void RemoveTexture(ref CTextureRef textureRef)
- {
- if (textureRef == null)
- return;
- if (textureRef.ID > 0)
- {
- if (_MainThreadID != Thread.CurrentThread.ManagedThreadId)
- {
- lock (_TextureQueue)
- {
- _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Delete, null));
- }
- textureRef = null;
- return;
- }
- lock (_Textures)
- {
- TTextureType t;
- if (_Textures.TryGetValue(textureRef.ID, out t))
- {
- _DisposeTexture(t);
- _Textures.Remove(textureRef.ID);
- }
- textureRef.SetRemoved();
- }
- }
- textureRef = null;
- }
-
- public int GetTextureCount()
- {
- return _TextureCount;
- }
- #endregion
// Resharper doesn't get that this is used -.-
// ReSharper disable UnusedMemberHiearchy.Global
diff --git a/Vocaluxe/Lib/Draw/CTextureProvider.cs b/Vocaluxe/Lib/Draw/CTextureProvider.cs
new file mode 100644
index 00000000..8a2f31a0
--- /dev/null
+++ b/Vocaluxe/Lib/Draw/CTextureProvider.cs
@@ -0,0 +1,886 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it 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.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Vocaluxe.Base;
+using VocaluxeLib;
+using VocaluxeLib.Draw;
+
+namespace Vocaluxe.Lib.Draw
+{
+ ///
+ /// Base class for graphic drivers containing the handling of textures
+ /// A few notes:
+ /// Writes to textures (TTextureType) should only be done in the main thread
+ /// Writes to the RefCount of existing textures should only be done in the main thread and guarded by _Textures lock
+ ///
+ ///
+ abstract class CTextureProvider where TTextureType : CTextureBase, IDisposable
+ {
+ private enum EQueueAction
+ {
+ Add,
+ Update,
+ Delete
+ }
+
+ private struct STextureQueue
+ {
+ public readonly object TextureOrRef;
+ ///
+ /// Only valid if Data is byte[]
+ ///
+ public readonly Size DataSize;
+ public readonly object Data;
+ public readonly EQueueAction Action;
+
+ ///
+ /// Creates a new Queue entry
+ ///
+ /// TTextureType or CTextureRef
+ ///
+ /// Texture data as a bitmap
+ public STextureQueue(object textureOrRef, EQueueAction action, Bitmap data)
+ {
+ TextureOrRef = textureOrRef;
+ Action = action;
+ Data = data;
+ DataSize = new Size();
+ }
+
+ ///
+ /// Creates a new Queue entry
+ ///
+ /// TTextureType or CTextureRef
+ ///
+ /// Size of data
+ /// Texture data
+ public STextureQueue(object textureOrRef, EQueueAction action, Size dataSize, byte[] data)
+ {
+ TextureOrRef = textureOrRef;
+ Action = action;
+ DataSize = dataSize;
+ Data = data;
+ }
+ }
+
+ private struct STextureCacheEntry
+ {
+ public TTextureType Texture;
+ ///
+ /// Size of original image (e.g. of bmp)
+ ///
+ public Size OrigSize;
+ }
+
+ protected bool _NonPowerOf2TextureSupported;
+
+ 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)
+ private readonly Dictionary _TextureCache = new Dictionary();
+ private readonly Queue _TextureQueue = new Queue();
+ private readonly Dictionary> _BitmapsLoading = new Dictionary>();
+ private int _TextureCount;
+
+ private int _MainThreadID;
+
+ public virtual bool Init()
+ {
+ _MainThreadID = Thread.CurrentThread.ManagedThreadId;
+ return true;
+ }
+
+ public virtual void Unload()
+ {
+ _EnsureMainThread();
+ lock (_Textures)
+ {
+ //Dispose all textures
+ foreach (TTextureType texture in _Textures.Values)
+ {
+ if (texture != null && --texture.RefCount <= 0)
+ texture.Dispose();
+ }
+ _Textures.Clear();
+ _TextureCache.Clear();
+ }
+ }
+
+ ///
+ /// This makes sure, the function is called from within the main thread
+ ///
+ protected void _EnsureMainThread()
+ {
+ Debug.Assert(_MainThreadID == Thread.CurrentThread.ManagedThreadId);
+ }
+
+ ///
+ /// Calculates the next power of two if the device has the POW2 flag set
+ ///
+ /// The value of which the next power of two will be calculated
+ /// The next power of two
+ protected int _CheckForNextPowerOf2(int n)
+ {
+ if (_NonPowerOf2TextureSupported)
+ return n;
+ if (n < 0)
+ throw new ArgumentOutOfRangeException("n", "Must be positive.");
+ return (int)Math.Pow(2, Math.Ceiling(Math.Log(n, 2)));
+ }
+
+ ///
+ /// Returns the maximum area allowed for a texture based on the current quality
+ ///
+ ///
+ private static int _GetMaxTextureArea()
+ {
+ switch (CConfig.TextureQuality)
+ {
+ case ETextureQuality.TR_CONFIG_TEXTURE_LOWEST:
+ return 128 * 128;
+ case ETextureQuality.TR_CONFIG_TEXTURE_LOW:
+ return 256 * 128;
+ case ETextureQuality.TR_CONFIG_TEXTURE_MEDIUM:
+ return 512 * 256;
+ case ETextureQuality.TR_CONFIG_TEXTURE_HIGH:
+ return 1024 * 512;
+ case ETextureQuality.TR_CONFIG_TEXTURE_HIGHEST:
+ return CSettings.RenderW * CSettings.RenderH;
+ default:
+ throw new ArgumentException();
+ }
+ }
+
+ ///
+ /// 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
+ ///
+ /// New size for the texture
+ private Size _GetNewTextureSize(Size size)
+ {
+ Debug.Assert(size.Width > 0 && size.Height > 0);
+ int maxArea = _GetMaxTextureArea();
+ 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)
+ {
+ if (size.Width < size.Height)
+ {
+ newSize.Width = _CheckForNextPowerOf2(newSize.Width);
+ newSize.Height = _CheckForNextPowerOf2(maxArea / newSize.Width);
+ if (newSize.Width * newSize.Height > maxArea)
+ newSize.Height /= 2;
+ }
+ else
+ {
+ newSize.Height = _CheckForNextPowerOf2(newSize.Height);
+ newSize.Width = _CheckForNextPowerOf2(maxArea / newSize.Height);
+ if (newSize.Width * newSize.Height > maxArea)
+ newSize.Width /= 2;
+ }
+ }
+ Debug.Assert(newSize.Width * newSize.Height <= maxArea);
+ return newSize;
+ }
+
+ protected abstract void _WriteDataToTexture(TTextureType texture, byte[] data);
+
+ protected virtual void _WriteDataToTexture(TTextureType texture, IntPtr data)
+ {
+ byte[] dataArray = new byte[4 * texture.DataSize.Width * texture.DataSize.Height];
+ Marshal.Copy(data, dataArray, 0, dataArray.Length);
+ _WriteDataToTexture(texture, dataArray);
+ }
+
+ ///
+ /// Factory method to create a texture of the actual type
+ /// The implementer has to create an invalid texture (IsLoaded=false) if dataSize=(-1,-1)
+ ///
+ ///
+ ///
+ protected abstract TTextureType _CreateTexture(Size dataSize);
+
+ ///
+ /// Creates the texture specified by the reference and fills it with the given data
+ ///
+ ///
+ ///
+ ///
+ private TTextureType _CreateAndFillTexture(Size dataSize, byte[] data)
+ {
+ TTextureType texture = _CreateTexture(dataSize);
+ _WriteDataToTexture(texture, data);
+ return texture;
+ }
+
+ ///
+ /// Writes the specified bitmap to the texture, creating it if null
+ /// Also does the resizing if required
+ ///
+ ///
+ ///
+ private void _WriteBitmapToTexture(ref TTextureType texture, Bitmap bmp)
+ {
+ Bitmap bmp2 = null;
+ try
+ {
+ Size size = _GetNewTextureSize(bmp.GetSize());
+ if (!size.Equals(bmp.GetSize()))
+ {
+ bmp2 = bmp.Resize(size);
+ bmp = bmp2;
+ }
+
+ //Fill the new Bitmap with the texture data
+ BitmapData bmpData = bmp.LockBits(bmp.GetRect(), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ if (texture == null)
+ texture = _CreateTexture(size);
+ else
+ texture.DataSize = size;
+ _WriteDataToTexture(texture, bmpData.Scan0);
+ bmp.UnlockBits(bmpData);
+ }
+ finally
+ {
+ if (bmp2 != null)
+ bmp2.Dispose();
+ }
+ }
+
+ ///
+ /// Adds the texture to the cache
+ /// Asserts that the cache entry does not exist yet. So make Get/Add cache atomic through use of _TextureCache lock!
+ /// Thread safe
+ ///
+ ///
+ ///
+ ///
+ private void _AddToCache(TTextureType texture, Size origSize, string 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};
+ _TextureCache.Add(texturePath, cacheEntry);
+ }
+ }
+
+ ///
+ /// Gets a new texture reference from the cache
+ /// Thread safe
+ ///
+ ///
+ /// Task that loads the texture or null if not loading
+ ///
+ private CTextureRef _GetFromCache(string texturePath, out Task loader)
+ {
+ loader = null;
+ if (String.IsNullOrEmpty(texturePath))
+ return null;
+ lock (_TextureCache)
+ lock (_Textures)
+ {
+ STextureCacheEntry cacheEntry;
+ if (_TextureCache.TryGetValue(texturePath, out cacheEntry))
+ {
+ if (cacheEntry.Texture.RefCount <= 0)
+ _TextureCache.Remove(texturePath);
+ else
+ {
+ if (cacheEntry.OrigSize.Width < 0)
+ {
+ lock (_BitmapsLoading)
+ {
+ loader = _BitmapsLoading[texturePath];
+ }
+ }
+ return _GetTextureReference(cacheEntry.OrigSize, cacheEntry.Texture);
+ }
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ ///
+ 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);
+ 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
+ /// Thread safe
+ /// Convenience method
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected CTextureRef _GetTextureReference(int origWidth, int origHeight, TTextureType texture)
+ {
+ return _GetTextureReference(new Size(origWidth, origHeight), texture);
+ }
+
+ private static bool _IsTextureUsable(TTextureType texture, int dataWidth, int dataHeight)
+ {
+ if (texture == null)
+ return false;
+ if (texture.DataSize.Width != dataWidth || texture.DataSize.Height != dataHeight)
+ {
+ if (texture.W2 > dataWidth || texture.H2 > dataHeight)
+ return false; // Texture memory to small
+ if (texture.W2 * 0.9 < dataWidth || texture.H2 * 0.9 < dataHeight)
+ return false; // Texture memory to big
+ }
+ return true;
+ }
+
+ protected bool _GetTexture(CTextureRef textureRef, out TTextureType texture, bool checkDrawable = true)
+ {
+ if (textureRef == null)
+ {
+ texture = null;
+ return false;
+ }
+ lock (_Textures)
+ {
+ return _Textures.TryGetValue(textureRef.ID, out texture) && (!checkDrawable || texture.IsLoaded);
+ }
+ }
+
+ ///
+ /// Decreases the RefCount of a texture and disposes it if necessary
+ ///
+ ///
+ private void _DisposeTexture(TTextureType texture)
+ {
+ _EnsureMainThread();
+ lock (_Textures)
+ {
+ if (texture != null && --texture.RefCount <= 0)
+ {
+ if (texture.TexturePath != null)
+ _TextureCache.Remove(texture.TexturePath);
+ texture.Dispose();
+ _TextureCount--;
+ }
+ }
+ }
+
+ protected void _CheckQueue()
+ {
+ _EnsureMainThread();
+ lock (_TextureQueue)
+ {
+ while (_TextureQueue.Count > 0)
+ {
+ STextureQueue q = _TextureQueue.Dequeue();
+
+ if (q.Action == EQueueAction.Add)
+ {
+ TTextureType oldTexture = q.TextureOrRef as TTextureType;
+ Debug.Assert(oldTexture != null, "Queued type is wrong");
+ Debug.Assert(!oldTexture.IsLoaded);
+ if (oldTexture.RefCount <= 0)
+ {
+ Bitmap bmp = q.Data as Bitmap;
+ if (bmp != null)
+ bmp.Dispose();
+ continue;
+ }
+ TTextureType texture = null;
+ // ReSharper disable CanBeReplacedWithTryCastAndCheckForNull
+ if (q.Data is Bitmap)
+ {
+ Bitmap bmp = (Bitmap)q.Data;
+ _WriteBitmapToTexture(ref texture, bmp);
+ bmp.Dispose();
+ }
+ else if (q.Data is byte[])
+ texture = _CreateAndFillTexture(q.DataSize, (byte[])q.Data);
+ else
+ throw new ArgumentException("q.Data is of invalid type");
+ // ReSharper restore CanBeReplacedWithTryCastAndCheckForNull
+ _MergeTextures(oldTexture, texture);
+ }
+ else
+ {
+ CTextureRef textureRef = q.TextureOrRef as CTextureRef;
+ Debug.Assert(textureRef != null, "Queued type is wrong");
+ if (!_Textures.ContainsKey(textureRef.ID))
+ {
+ Bitmap bmp = q.Data as Bitmap;
+ if (bmp != null)
+ bmp.Dispose();
+ continue;
+ }
+ if (q.Action == EQueueAction.Update)
+ {
+ // ReSharper disable CanBeReplacedWithTryCastAndCheckForNull
+ if (q.Data is Bitmap)
+ {
+ Bitmap bmp = (Bitmap)q.Data;
+ UpdateTexture(textureRef, bmp);
+ bmp.Dispose();
+ }
+ else if (q.Data is byte[])
+ UpdateTexture(textureRef, q.DataSize.Width, q.DataSize.Height, (byte[])q.Data);
+ else
+ 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
+ ///
+ ///
+ ///
+ private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture)
+ {
+ _EnsureMainThread();
+ Debug.Assert(oldTexture.RefCount > 0);
+ lock (_Textures)
+ {
+ newTexture.RefCount += oldTexture.RefCount;
+ IEnumerable oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray();
+ foreach (int key in oldKeys)
+ _Textures[key] = newTexture;
+ if (!String.IsNullOrEmpty(oldTexture.TexturePath))
+ {
+ newTexture.TexturePath = oldTexture.TexturePath;
+ STextureCacheEntry cacheEntry;
+ if (_TextureCache.TryGetValue(newTexture.TexturePath, out cacheEntry))
+ {
+ cacheEntry.Texture = newTexture;
+ _TextureCache[newTexture.TexturePath] = cacheEntry;
+ }
+ }
+ oldTexture.Dispose();
+ }
+ }
+
+ ///
+ /// Enqueues a bitmap to add or update the texture
+ ///
+ /// TTextureType (Add) or CTextureRef(Update)
+ ///
+ ///
+ /// True if resizing should be done in an extra thread
+ private void _EnqueueTextureAddOrUpdate(Object texture, Bitmap bmp, EQueueAction action, bool asyncResize)
+ {
+ Debug.Assert(action == EQueueAction.Add || action == EQueueAction.Update);
+ Debug.Assert(action != EQueueAction.Add || texture is TTextureType);
+ Debug.Assert(action != EQueueAction.Update || texture is CTextureRef);
+ if (_RequiresResize(bmp.GetSize()))
+ {
+ if (asyncResize)
+ Task.Factory.StartNew(() => _ResizeTextureAndEnqueue(texture, bmp, action));
+ else
+ _ResizeTextureAndEnqueue(texture, bmp, action);
+ }
+ else
+ {
+ lock (_TextureQueue)
+ {
+ _TextureQueue.Enqueue(new STextureQueue(texture, action, bmp));
+ }
+ }
+ }
+
+ ///
+ /// Helper function to resize a bitmap and enqueue it
+ ///
+ ///
+ ///
+ ///
+ private void _ResizeTextureAndEnqueue(object texture, Bitmap bmp, EQueueAction action)
+ {
+ Bitmap bmp2 = bmp.Resize(_GetNewTextureSize(bmp.Size));
+ bmp.Dispose();
+ lock (_TextureQueue)
+ {
+ _TextureQueue.Enqueue(new STextureQueue(texture, action, bmp2));
+ }
+ }
+
+ ///
+ /// Helper function for loading a bitmap in a task
+ ///
+ ///
+ ///
+ ///
+ ///
+ private Size _LoadAndEnqueueBitmap(string filePath, CTextureRef textureRef, TTextureType texture)
+ {
+ Bitmap bmp = CHelper.LoadBitmap(filePath);
+ if (bmp == null)
+ {
+ RemoveTexture(ref textureRef); // Done asynchonously in the function
+ lock (_Textures)
+ {
+ _TextureCache.Remove(filePath);
+ lock (_BitmapsLoading)
+ {
+ _BitmapsLoading.Remove(filePath);
+ }
+ }
+ return new Size(-1, -1);
+ }
+ Size 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;
+ }
+ lock (_BitmapsLoading)
+ {
+ _BitmapsLoading.Remove(filePath);
+ }
+ }
+ _EnqueueTextureAddOrUpdate(texture, bmp, EQueueAction.Add, false);
+ return origSize;
+ }
+
+ ///
+ /// Adds a texture and stores it in the VRam
+ ///
+ /// The texture's filepath
+ /// A STexture object containing the added texture
+ public CTextureRef AddTexture(string texturePath)
+ {
+ _EnsureMainThread();
+ CTextureRef textureRef;
+ Task loader;
+ lock (_TextureCache)
+ {
+ textureRef = _GetFromCache(texturePath, out loader);
+ }
+ if (textureRef != null)
+ {
+ if (loader != null)
+ textureRef.OrigSize = loader.Result;
+ Debug.Assert(textureRef.OrigSize.Width > 0);
+ return textureRef;
+ }
+
+ Bitmap bmp = CHelper.LoadBitmap(texturePath);
+ if (bmp == null)
+ return null;
+ try
+ {
+ textureRef = AddTexture(bmp, texturePath);
+ }
+ finally
+ {
+ bmp.Dispose();
+ }
+ return textureRef;
+ }
+
+ public CTextureRef AddTexture(Bitmap bmp)
+ {
+ _EnsureMainThread();
+ return AddTexture(bmp, null);
+ }
+
+ ///
+ /// Adds a texture and stores it in the Vram
+ ///
+ /// The Bitmap of which the texure will be created from
+ ///
+ /// 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;
+ Task loader;
+ // Make the Get/Add Cache methods atomic
+ lock (_TextureCache)
+ {
+ textureRef = _GetFromCache(texturePath, out loader);
+ if (textureRef == null)
+ {
+ _WriteBitmapToTexture(ref texture, bmp);
+
+ _AddToCache(texture, origSize, texturePath);
+ }
+ }
+ if (textureRef == null)
+ textureRef = _GetTextureReference(origSize, texture);
+ else if (loader != null)
+ textureRef.OrigSize = loader.Result;
+ 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 (_TextureQueue)
+ {
+ TTextureType texture = _CreateTexture(new Size(-1, -1));
+ CTextureRef textureRef = _GetTextureReference(w, h, texture);
+ _TextureQueue.Enqueue(new STextureQueue(texture, EQueueAction.Add, new Size(w, h), data));
+ return textureRef;
+ }
+ }
+
+ public CTextureRef EnqueueTexture(Bitmap bmp)
+ {
+ TTextureType texture = _CreateTexture(new Size(-1, -1));
+ CTextureRef textureRef = _GetTextureReference(bmp.GetSize(), texture);
+ _EnqueueTextureAddOrUpdate(texture, bmp, EQueueAction.Add, true);
+
+ 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;
+ // Make Get/Add cache atomic
+ lock (_TextureCache)
+ {
+ textureRef = _GetFromCache(filePath, out loader);
+ if (textureRef == null)
+ {
+ Debug.Assert(loader == 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, texture));
+ lock (_BitmapsLoading)
+ {
+ _BitmapsLoading.Add(filePath, loader);
+ }
+ }
+ }
+
+ if (texture == null)
+ {
+ // Found in cache
+ CTextureRef tmp = textureRef; // Workaround for implicitly captured closure warning (false positive)
+ if (loader != null)
+ Task.Factory.StartNew(() => tmp.OrigSize = loader.Result);
+ return textureRef;
+ }
+
+ Debug.Assert(loader != null);
+ Debug.Assert(texture != null);
+ loader.Start();
+ return textureRef;
+ }
+
+ public void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp)
+ {
+ _EnqueueTextureAddOrUpdate(textureRef, bmp, EQueueAction.Update, true);
+ }
+
+ ///
+ /// Updates the data of a texture
+ ///
+ /// The texture to update
+ ///
+ ///
+ /// A byte array containing the new texture's data
+ /// True if succeeded
+ public void UpdateTexture(CTextureRef textureRef, int w, int h, byte[] data)
+ {
+ _EnsureMainThread();
+ TTextureType texture;
+ if (!_GetTexture(textureRef, out texture, false))
+ return;
+ bool reuseTexture = _IsTextureUsable(texture, w, h);
+ if (reuseTexture && texture.RefCount == 1)
+ {
+ texture.DataSize = new Size(w, h);
+ _WriteDataToTexture(texture, data);
+ }
+ else
+ {
+ _DisposeTexture(texture);
+ texture = _CreateAndFillTexture(new Size(w, h), data);
+ texture.RefCount = 1;
+ _Textures[textureRef.ID] = texture;
+ }
+ }
+
+ public void UpdateTexture(CTextureRef textureRef, Bitmap bmp)
+ {
+ _EnsureMainThread();
+ TTextureType texture;
+ if (!_GetTexture(textureRef, out texture, false))
+ return;
+ bool reuseTexture = _IsTextureUsable(texture, bmp.Width, bmp.Height);
+ if (reuseTexture && texture.RefCount == 1)
+ _WriteBitmapToTexture(ref texture, bmp);
+ else
+ {
+ _DisposeTexture(texture);
+ texture = null;
+ _WriteBitmapToTexture(ref texture, bmp);
+ texture.RefCount = 1;
+ _Textures[textureRef.ID] = texture;
+ }
+ }
+
+ public CTextureRef CopyTexture(CTextureRef textureRef)
+ {
+ _EnsureMainThread();
+ TTextureType texture;
+ if (!_GetTexture(textureRef, out texture, false))
+ return null;
+ // If bitmap is not yet loaded, wait for it
+ if (!String.IsNullOrEmpty(texture.TexturePath))
+ {
+ Task loader;
+ lock (_BitmapsLoading)
+ {
+ _BitmapsLoading.TryGetValue(texture.TexturePath, out loader);
+ }
+ if (loader != null)
+ textureRef.OrigSize = loader.Result;
+ }
+ Debug.Assert(textureRef.OrigSize.Width > 0);
+ return _GetTextureReference(textureRef.OrigSize, texture);
+ }
+
+ ///
+ /// Removes a texture from the Vram
+ ///
+ /// The texture to be removed
+ public void RemoveTexture(ref CTextureRef textureRef)
+ {
+ if (textureRef == null)
+ return;
+ if (textureRef.ID > 0)
+ {
+ if (_MainThreadID != Thread.CurrentThread.ManagedThreadId)
+ {
+ lock (_TextureQueue)
+ {
+ _TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Delete, null));
+ }
+ textureRef = null;
+ return;
+ }
+ lock (_Textures)
+ {
+ TTextureType t;
+ if (_Textures.TryGetValue(textureRef.ID, out t))
+ {
+ _DisposeTexture(t);
+ _Textures.Remove(textureRef.ID);
+ }
+ textureRef.SetRemoved();
+ }
+ }
+ textureRef = null;
+ }
+
+ public int GetTextureCount()
+ {
+ return _TextureCount;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj
index 5a54ddfb..a47f4c5c 100644
--- a/Vocaluxe/Vocaluxe.csproj
+++ b/Vocaluxe/Vocaluxe.csproj
@@ -227,6 +227,7 @@
+