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) public void Print(TextBlock block, IGlyphRasterizer rasterizer, GlyphCache cache)
{ {
TextExtents extents = rasterizer.MeasureText(block); using (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)
{ {
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++; if (glyph.IsWhiteSpace)
continue; {
} current++;
else if (!cache.Contains(glyph)) continue;
cache.Add(glyph); }
else if (!cache.Contains(glyph))
cache.Add(glyph);
CachedGlyphInfo info = cache[glyph]; CachedGlyphInfo info = cache[glyph];
RectangleF position = extents[current++]; 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;
if (!active_lists.ContainsKey(info.Texture)) // Use the real glyph width instead of the measured one (we want to achieve pixel perfect output).
if (inactive_lists.Count > 0) position.Size = info.Rectangle.Size;
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)); if (!active_lists.ContainsKey(info.Texture))
active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom)); if (inactive_lists.Count > 0)
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Top)); active_lists.Add(info.Texture, inactive_lists.Dequeue());
active_lists[info.Texture].Add(new Vector2(position.Right, position.Top)); else
active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top)); active_lists.Add(info.Texture, new List<Vector2>());
active_lists[info.Texture].Add(new Vector2(position.Left, position.Top)); {
// 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.Graphics.Text;
using OpenTK.Platform; using OpenTK.Platform;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing.Imaging;
namespace OpenTK.Graphics.Text namespace OpenTK.Graphics.Text
{ {
@ -47,11 +48,11 @@ namespace OpenTK.Graphics.Text
IntPtr[] regions = new IntPtr[GdiPlus.MaxMeasurableCharacterRanges]; IntPtr[] regions = new IntPtr[GdiPlus.MaxMeasurableCharacterRanges];
CharacterRange[] characterRanges = new CharacterRange[GdiPlus.MaxMeasurableCharacterRanges]; CharacterRange[] characterRanges = new CharacterRange[GdiPlus.MaxMeasurableCharacterRanges];
TextExtents extents = new TextExtents();
Bitmap glyph_surface; Bitmap glyph_surface;
System.Drawing.Graphics glyph_renderer; System.Drawing.Graphics glyph_renderer;
readonly ObjectPool<PoolableTextExtents> text_extents_pool = new ObjectPool<PoolableTextExtents>();
// Check the constructor, too, for additional flags. // Check the constructor, too, for additional flags.
static readonly StringFormat default_string_format = StringFormat.GenericTypographic; static readonly StringFormat default_string_format = StringFormat.GenericTypographic;
static readonly StringFormat load_glyph_string_format = StringFormat.GenericDefault; static readonly StringFormat load_glyph_string_format = StringFormat.GenericDefault;
@ -78,6 +79,8 @@ namespace OpenTK.Graphics.Text
public Bitmap Rasterize(Glyph glyph) 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); EnsureSurfaceSize(ref glyph_surface, ref glyph_renderer, glyph.Font);
SetTextRenderingOptions(glyph_renderer, glyph.Font); SetTextRenderingOptions(glyph_renderer, glyph.Font);
@ -85,7 +88,25 @@ namespace OpenTK.Graphics.Text
glyph_renderer.Clear(Color.Transparent); glyph_renderer.Clear(Color.Transparent);
glyph_renderer.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty, glyph_renderer.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty,
glyph.Font.Style == FontStyle.Italic ? load_glyph_string_format : default_string_format); 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 #endregion
@ -99,10 +120,10 @@ namespace OpenTK.Graphics.Text
if (block_cache.ContainsKey(block)) if (block_cache.ContainsKey(block))
return block_cache[block]; return block_cache[block];
// If this block is not cached, we have to measure it and place it in the cache. // If this block is not cached, we have to measure it and (potentially) place it in the cache.
MeasureTextExtents(block, ref extents); TextExtents extents = MeasureTextExtents(block);
if ((block.Options & TextPrinterOptions.NoCache) == 0) if ((block.Options & TextPrinterOptions.NoCache) == 0)
block_cache.Add(block, new TextExtents(extents.BoundingBox, extents.GlyphExtents)); block_cache.Add(block, extents);
return extents; return extents;
} }
@ -147,7 +168,7 @@ namespace OpenTK.Graphics.Text
#region MeasureTextExtents #region MeasureTextExtents
void MeasureTextExtents(TextBlock block, ref TextExtents extents) TextExtents MeasureTextExtents(TextBlock block)
{ {
// Todo: Parse layout options: // Todo: Parse layout options:
StringFormat format = default_string_format; StringFormat format = default_string_format;
@ -156,7 +177,7 @@ namespace OpenTK.Graphics.Text
//else //else
// format = default_string_format; // format = default_string_format;
extents.Clear(); TextExtents extents = text_extents_pool.Acquire();
PointF origin = PointF.Empty; PointF origin = PointF.Empty;
SizeF size = SizeF.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); extents.BoundingBox = new RectangleF(extents[0].X, extents[0].Y, extents[extents.Count - 1].Right, extents[extents.Count - 1].Bottom);
return extents;
} }
#endregion #endregion
@ -252,13 +275,28 @@ namespace OpenTK.Graphics.Text
#region FindEdges #region FindEdges
#pragma warning disable 0649
struct Pixel { public byte B, G, R, A; }
#pragma warning restore 0649
Rectangle FindEdges(Bitmap bmp) Rectangle FindEdges(Bitmap bmp)
{ {
return Rectangle.FromLTRB( BitmapData data = bmp.LockBits(
FindLeftEdge(bmp), new Rectangle(0, 0, bmp.Width, bmp.Height),
FindTopEdge(bmp), ImageLockMode.ReadOnly,
FindRightEdge(bmp), System.Drawing.Imaging.PixelFormat.Format32bppArgb);
FindBottomEdge(bmp));
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 #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. // 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. // Don't trim the left edge, because the layout engine expects it to be 0.
return 0; return 0;
} }
int FindRightEdge(Bitmap bmp) int FindRightEdge(Bitmap bmp, IntPtr ptr)
{ {
for (int x = bmp.Width - 1; x >= 0; x--) for (int x = bmp.Width - 1; x >= 0; x--)
for (int y = 0; y < bmp.Height; y++) for (int y = 0; y < bmp.Height; y++)
if (bmp.GetPixel(x, y).A != 0) unsafe
return x + 1; {
if (((Pixel*)(ptr) + y * bmp.Width + x)->A != 0)
return x + 1;
}
return 0; 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. // Don't trim the top edge, because the layout engine expects it to be 0.
return 0; return 0;
} }
int FindBottomEdge(Bitmap bmp) int FindBottomEdge(Bitmap bmp, IntPtr ptr)
{ {
for (int y = bmp.Height - 1; y >= 0; y--) for (int y = bmp.Height - 1; y >= 0; y--)
for (int x = 0; x < bmp.Width; x++) for (int x = 0; x < bmp.Width; x++)
if (bmp.GetPixel(x, y).A != 0) unsafe
return y + 1; {
if (((Pixel*)(ptr) + y * bmp.Width + x)->A != 0)
return y + 1;
}
return 0; return 0;
} }

View file

@ -38,5 +38,6 @@ namespace OpenTK.Graphics.Text
{ {
Bitmap Rasterize(Glyph glyph); Bitmap Rasterize(Glyph glyph);
TextExtents MeasureText(TextBlock block); 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 namespace OpenTK.Graphics.Text
{ {
// Holds layout information about a TextBlock. // Holds layout information about a TextBlock.
public struct TextExtents public class TextExtents : IDisposable
{ {
#region Fields #region Fields
RectangleF text_extents; protected RectangleF text_extents;
List<RectangleF> glyph_extents; protected List<RectangleF> glyph_extents = new List<RectangleF>();
#endregion #endregion
#region Constructors #region Constructors
public TextExtents(RectangleF bbox) internal TextExtents()
: this(bbox, null)
{ {
} }
public TextExtents(RectangleF bbox, IEnumerable<RectangleF> glyphExtents)
: this()
{
BoundingBox = bbox;
if (glyphExtents != null)
AddRange(glyphExtents);
}
#endregion #endregion
#region Public Members #region Public Members
@ -70,23 +60,29 @@ namespace OpenTK.Graphics.Text
public RectangleF this[int i] public RectangleF this[int i]
{ {
get { return (GlyphExtents as List<RectangleF>)[i]; } get { return glyph_extents[i]; }
internal set { (GlyphExtents as List<RectangleF>)[i] = value; } internal set { glyph_extents[i] = value; }
} }
public IEnumerable<RectangleF> GlyphExtents public IEnumerable<RectangleF> GlyphExtents
{ {
get get
{ {
if (glyph_extents == null)
glyph_extents = new List<RectangleF>();
return (IEnumerable<RectangleF>)glyph_extents; return (IEnumerable<RectangleF>)glyph_extents;
} }
} }
public int Count 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 #endregion
@ -95,18 +91,26 @@ namespace OpenTK.Graphics.Text
internal void Add(RectangleF glyphExtent) internal void Add(RectangleF glyphExtent)
{ {
(GlyphExtents as List<RectangleF>).Add(glyphExtent); glyph_extents.Add(glyphExtent);
} }
internal void AddRange(IEnumerable<RectangleF> glyphExtents) internal void AddRange(IEnumerable<RectangleF> glyphExtents)
{ {
(GlyphExtents as List<RectangleF>).AddRange(glyphExtents); glyph_extents.AddRange(glyphExtents);
} }
internal void Clear() internal void Clear()
{ {
BoundingBox = RectangleF.Empty; BoundingBox = RectangleF.Empty;
(GlyphExtents as List<RectangleF>).Clear(); glyph_extents.Clear();
}
#endregion
#region IDisposable Members
public virtual void Dispose()
{
} }
#endregion #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);
}
}
}