Fix some race conditions with the texture references

Add queuing of files
Split CEncoding to separate file
This commit is contained in:
Flamefire committed 2014-09-23 21:37:46 +02:00
1 parent 1121261a4a
commit 649f81494e
11 files changed
+512 -195

No files matched your search

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