Fixed TextExtents behavior when returning either cached or uncached instances.

Reduced memory pressure by adding object pooling to TextExtents.
This commit is contained in:
the_fiddler 2008-11-26 16:34:50 +00:00
parent a868c4b4e8
commit c0549b11fa
7 changed files with 246 additions and 95 deletions

View file

@ -57,63 +57,64 @@ namespace OpenTK.Graphics.Text
public void Print(TextBlock block, IGlyphRasterizer rasterizer, GlyphCache cache)
{
TextExtents extents = rasterizer.MeasureText(block);
//GL.BindTexture(TextureTarget.Texture2D, 2);
//GL.Begin(BeginMode.Quads);
//GL.TexCoord2(0, 0);
//GL.Vertex2(0, 0);
//GL.TexCoord2(1, 0);
//GL.Vertex2(256, 0);
//GL.TexCoord2(1, 1);
//GL.Vertex2(256, 256);
//GL.TexCoord2(0, 1);
//GL.Vertex2(0, 256);
//GL.End();
//GL.Translate(0, 256, 0);
// Build layout
int current = 0;
foreach (Glyph glyph in block)
using (TextExtents extents = rasterizer.MeasureText(block))
{
if (glyph.IsWhiteSpace)
//GL.BindTexture(TextureTarget.Texture2D, 2);
//GL.Begin(BeginMode.Quads);
//GL.TexCoord2(0, 0);
//GL.Vertex2(0, 0);
//GL.TexCoord2(1, 0);
//GL.Vertex2(256, 0);
//GL.TexCoord2(1, 1);
//GL.Vertex2(256, 256);
//GL.TexCoord2(0, 1);
//GL.Vertex2(0, 256);
//GL.End();
//GL.Translate(0, 256, 0);
// Build layout
int current = 0;
foreach (Glyph glyph in block)
{
current++;
continue;
}
else if (!cache.Contains(glyph))
cache.Add(glyph);
if (glyph.IsWhiteSpace)
{
current++;
continue;
}
else if (!cache.Contains(glyph))
cache.Add(glyph);
CachedGlyphInfo info = cache[glyph];
RectangleF position = extents[current++];
// Use the real glyph width instead of the measured one (we want to achieve pixel perfect output).
position.Size = info.Rectangle.Size;
CachedGlyphInfo info = cache[glyph];
RectangleF position = extents[current++];
if (!active_lists.ContainsKey(info.Texture))
if (inactive_lists.Count > 0)
active_lists.Add(info.Texture, inactive_lists.Dequeue());
else
active_lists.Add(info.Texture, new List<Vector2>());
{
// Interleaved array: Vertex, TexCoord, Vertex, ...
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top));
active_lists[info.Texture].Add(new Vector2(position.Left, position.Top));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Bottom));
active_lists[info.Texture].Add(new Vector2(position.Left, position.Bottom));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom));
active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom));
// Use the real glyph width instead of the measured one (we want to achieve pixel perfect output).
position.Size = info.Rectangle.Size;
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom));
active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Top));
active_lists[info.Texture].Add(new Vector2(position.Right, position.Top));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top));
active_lists[info.Texture].Add(new Vector2(position.Left, position.Top));
if (!active_lists.ContainsKey(info.Texture))
if (inactive_lists.Count > 0)
active_lists.Add(info.Texture, inactive_lists.Dequeue());
else
active_lists.Add(info.Texture, new List<Vector2>());
{
// Interleaved array: Vertex, TexCoord, Vertex, ...
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top));
active_lists[info.Texture].Add(new Vector2(position.Left, position.Top));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Bottom));
active_lists[info.Texture].Add(new Vector2(position.Left, position.Bottom));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom));
active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom));
active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Top));
active_lists[info.Texture].Add(new Vector2(position.Right, position.Top));
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top));
active_lists[info.Texture].Add(new Vector2(position.Left, position.Top));
}
}
}

View file

@ -34,6 +34,7 @@ using System.Drawing.Text;
using OpenTK.Graphics.Text;
using OpenTK.Platform;
using System.Diagnostics;
using System.Drawing.Imaging;
namespace OpenTK.Graphics.Text
{
@ -47,11 +48,11 @@ namespace OpenTK.Graphics.Text
IntPtr[] regions = new IntPtr[GdiPlus.MaxMeasurableCharacterRanges];
CharacterRange[] characterRanges = new CharacterRange[GdiPlus.MaxMeasurableCharacterRanges];
TextExtents extents = new TextExtents();
Bitmap glyph_surface;
System.Drawing.Graphics glyph_renderer;
readonly ObjectPool<PoolableTextExtents> text_extents_pool = new ObjectPool<PoolableTextExtents>();
// Check the constructor, too, for additional flags.
static readonly StringFormat default_string_format = StringFormat.GenericTypographic;
static readonly StringFormat load_glyph_string_format = StringFormat.GenericDefault;
@ -78,6 +79,8 @@ namespace OpenTK.Graphics.Text
public Bitmap Rasterize(Glyph glyph)
{
RectangleF r = MeasureText(new TextBlock(glyph.Character.ToString(), glyph.Font, TextPrinterOptions.NoCache, RectangleF.Empty)).BoundingBox;
EnsureSurfaceSize(ref glyph_surface, ref glyph_renderer, glyph.Font);
SetTextRenderingOptions(glyph_renderer, glyph.Font);
@ -85,7 +88,25 @@ namespace OpenTK.Graphics.Text
glyph_renderer.Clear(Color.Transparent);
glyph_renderer.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty,
glyph.Font.Style == FontStyle.Italic ? load_glyph_string_format : default_string_format);
return glyph_surface.Clone(FindEdges(glyph_surface), System.Drawing.Imaging.PixelFormat.Format32bppArgb);
RectangleF r2 = FindEdges(glyph_surface);
return glyph_surface.Clone(r2, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
}
public void Rasterize(Glyph glyph, ref Bitmap bmp, out Rectangle rect)
{
EnsureSurfaceSize(ref bmp, ref glyph_renderer, glyph.Font);
using (System.Drawing.Graphics gfx = System.Drawing.Graphics.FromImage(bmp))
{
SetTextRenderingOptions(gfx, glyph.Font);
gfx.Clear(Color.Transparent);
gfx.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty,
glyph.Font.Style == FontStyle.Italic ? load_glyph_string_format : default_string_format);
rect = FindEdges(bmp);
}
}
#endregion
@ -99,10 +120,10 @@ namespace OpenTK.Graphics.Text
if (block_cache.ContainsKey(block))
return block_cache[block];
// If this block is not cached, we have to measure it and place it in the cache.
MeasureTextExtents(block, ref extents);
// If this block is not cached, we have to measure it and (potentially) place it in the cache.
TextExtents extents = MeasureTextExtents(block);
if ((block.Options & TextPrinterOptions.NoCache) == 0)
block_cache.Add(block, new TextExtents(extents.BoundingBox, extents.GlyphExtents));
block_cache.Add(block, extents);
return extents;
}
@ -147,7 +168,7 @@ namespace OpenTK.Graphics.Text
#region MeasureTextExtents
void MeasureTextExtents(TextBlock block, ref TextExtents extents)
TextExtents MeasureTextExtents(TextBlock block)
{
// Todo: Parse layout options:
StringFormat format = default_string_format;
@ -156,7 +177,7 @@ namespace OpenTK.Graphics.Text
//else
// format = default_string_format;
extents.Clear();
TextExtents extents = text_extents_pool.Acquire();
PointF origin = PointF.Empty;
SizeF size = SizeF.Empty;
@ -189,6 +210,8 @@ namespace OpenTK.Graphics.Text
}
extents.BoundingBox = new RectangleF(extents[0].X, extents[0].Y, extents[extents.Count - 1].Right, extents[extents.Count - 1].Bottom);
return extents;
}
#endregion
@ -252,13 +275,28 @@ namespace OpenTK.Graphics.Text
#region FindEdges
#pragma warning disable 0649
struct Pixel { public byte B, G, R, A; }
#pragma warning restore 0649
Rectangle FindEdges(Bitmap bmp)
{
return Rectangle.FromLTRB(
FindLeftEdge(bmp),
FindTopEdge(bmp),
FindRightEdge(bmp),
FindBottomEdge(bmp));
BitmapData data = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Rectangle rect = Rectangle.FromLTRB(
FindLeftEdge(bmp, data.Scan0),
FindTopEdge(bmp, data.Scan0),
FindRightEdge(bmp, data.Scan0),
FindBottomEdge(bmp, data.Scan0));
bmp.UnlockBits(data);
return rect;
}
#endregion
@ -267,34 +305,40 @@ namespace OpenTK.Graphics.Text
// Iterates through the bmp, and returns the first row or line that contains a non-transparent pixels.
int FindLeftEdge(Bitmap bmp)
int FindLeftEdge(Bitmap bmp, IntPtr ptr)
{
// Don't trim the left edge, because the layout engine expects it to be 0.
return 0;
}
int FindRightEdge(Bitmap bmp)
int FindRightEdge(Bitmap bmp, IntPtr ptr)
{
for (int x = bmp.Width - 1; x >= 0; x--)
for (int y = 0; y < bmp.Height; y++)
if (bmp.GetPixel(x, y).A != 0)
return x + 1;
unsafe
{
if (((Pixel*)(ptr) + y * bmp.Width + x)->A != 0)
return x + 1;
}
return 0;
}
int FindTopEdge(Bitmap bmp)
int FindTopEdge(Bitmap bmp, IntPtr ptr)
{
// Don't trim the top edge, because the layout engine expects it to be 0.
return 0;
}
int FindBottomEdge(Bitmap bmp)
int FindBottomEdge(Bitmap bmp, IntPtr ptr)
{
for (int y = bmp.Height - 1; y >= 0; y--)
for (int x = 0; x < bmp.Width; x++)
if (bmp.GetPixel(x, y).A != 0)
return y + 1;
unsafe
{
if (((Pixel*)(ptr) + y * bmp.Width + x)->A != 0)
return y + 1;
}
return 0;
}

View file

@ -38,5 +38,6 @@ namespace OpenTK.Graphics.Text
{
Bitmap Rasterize(Glyph glyph);
TextExtents MeasureText(TextBlock block);
void Rasterize(Glyph glyph, ref Bitmap bmp, out Rectangle rect);
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics.Text
{
class PoolableTextExtents : TextExtents, IPoolable<PoolableTextExtents>
{
ObjectPool<PoolableTextExtents> owner;
#region Constructors
public PoolableTextExtents()
{
}
#endregion
#region IPoolable<PoolableTextExtents> Members
ObjectPool<PoolableTextExtents> IPoolable<PoolableTextExtents>.Owner
{
get { return owner; }
set { owner = value; }
}
#endregion
#region IPoolable Members
void IPoolable.OnAcquire()
{
Clear();
}
void IPoolable.OnRelease()
{
}
#endregion
}
}

View file

@ -33,31 +33,21 @@ using System.Drawing;
namespace OpenTK.Graphics.Text
{
// Holds layout information about a TextBlock.
public struct TextExtents
public class TextExtents : IDisposable
{
#region Fields
RectangleF text_extents;
List<RectangleF> glyph_extents;
protected RectangleF text_extents;
protected List<RectangleF> glyph_extents = new List<RectangleF>();
#endregion
#region Constructors
public TextExtents(RectangleF bbox)
: this(bbox, null)
internal TextExtents()
{
}
public TextExtents(RectangleF bbox, IEnumerable<RectangleF> glyphExtents)
: this()
{
BoundingBox = bbox;
if (glyphExtents != null)
AddRange(glyphExtents);
}
#endregion
#region Public Members
@ -70,23 +60,29 @@ namespace OpenTK.Graphics.Text
public RectangleF this[int i]
{
get { return (GlyphExtents as List<RectangleF>)[i]; }
internal set { (GlyphExtents as List<RectangleF>)[i] = value; }
get { return glyph_extents[i]; }
internal set { glyph_extents[i] = value; }
}
public IEnumerable<RectangleF> GlyphExtents
{
get
{
if (glyph_extents == null)
glyph_extents = new List<RectangleF>();
return (IEnumerable<RectangleF>)glyph_extents;
}
}
public int Count
{
get { return (GlyphExtents as List<RectangleF>).Count; }
get { return glyph_extents.Count; }
}
public TextExtents Clone()
{
TextExtents extents = new TextExtents();
extents.glyph_extents.AddRange(GlyphExtents);
extents.BoundingBox = BoundingBox;
return extents;
}
#endregion
@ -95,18 +91,26 @@ namespace OpenTK.Graphics.Text
internal void Add(RectangleF glyphExtent)
{
(GlyphExtents as List<RectangleF>).Add(glyphExtent);
glyph_extents.Add(glyphExtent);
}
internal void AddRange(IEnumerable<RectangleF> glyphExtents)
{
(GlyphExtents as List<RectangleF>).AddRange(glyphExtents);
glyph_extents.AddRange(glyphExtents);
}
internal void Clear()
{
BoundingBox = RectangleF.Empty;
(GlyphExtents as List<RectangleF>).Clear();
glyph_extents.Clear();
}
#endregion
#region IDisposable Members
public virtual void Dispose()
{
}
#endregion

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK
{
interface IPoolable : IDisposable
{
void OnAcquire();
void OnRelease();
}
interface IPoolable<T> : IPoolable where T : IPoolable<T>, new()
{
ObjectPool<T> Owner { get; set; }
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK
{
class ObjectPool<T> where T : IPoolable<T>, new()
{
Queue<T> pool = new Queue<T>();
public ObjectPool()
{ }
public T Acquire()
{
T item;
if (pool.Count > 0)
{
item = pool.Dequeue();
item.OnAcquire();
}
else
{
item = new T();
item.Owner = this;
item.OnAcquire();
}
return item;
}
public void Release(T item)
{
if (item == null)
throw new ArgumentNullException("item");
item.OnRelease();
pool.Enqueue(item);
}
}
}