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 @@ +