diff --git a/Source/OpenTK/Fonts/DefaultLayoutProvider.cs b/Source/OpenTK/Fonts/DefaultLayoutProvider.cs index fb24c349..4400e513 100644 --- a/Source/OpenTK/Fonts/DefaultLayoutProvider.cs +++ b/Source/OpenTK/Fonts/DefaultLayoutProvider.cs @@ -1,4 +1,10 @@ -using System; +#region --- License --- +/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos + * See license.txt for license info + */ +#endregion + +using System; using System.Collections.Generic; using System.Text; using System.Drawing; @@ -6,69 +12,137 @@ using System.Text.RegularExpressions; using OpenTK.Math; using OpenTK.OpenGL; +using OpenTK.OpenGL.Enums; +using System.Runtime.InteropServices; namespace OpenTK.Fonts { - public class DefaultLayoutProvider : ILayoutProvider + /// + /// Provides methods to perform layout and print hardware accelerated text. + /// + public class TextPrinter : ITextPrinter { - static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - static char[] split_chars = new char[] { ' ', ',', '.', '/', '?', '!', ';', '\\', '-', '+', '*', '=' }; + //static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + //static char[] split_chars = new char[] { ' ', '\n', '\t', ',', '.', '/', '?', '!', ';', '\\', '-', '+', '*', '=' }; + static bool use_vbo, use_arb_vbo, use_display_list; + static bool functionality_checked = false; + static IPrinter printer; - #region --- ILayoutProvider Members --- + #region --- Constructor --- - public void PerformLayout(string text, IFont font, RectangleF layoutRect, StringAlignment alignment, bool rightToLeft) + /// + /// Constructs a new DefaultLayoutProvider object. + /// + public TextPrinter() { } + + #endregion + + /// + /// Checks the machine's capabilities and selects the fastest method to print text. + /// + static void CheckNeededFunctionality() { - Vector2[] vertices = new Vector2[2 * text.Length + 4]; - float x_pos, y_pos; - int i = 0, count, last_break_point = 0; + printer = + GL.SupportsExtension("VERSION_1_5") ? new VboPrinter() : + GL.SupportsExtension("ARB_vertex_buffer_object") ? null : + GL.SupportsExtension("VERSION_1_1") ? null : null; + if (printer == null) + throw new NotSupportedException("DefaultLayoutProvider requires at least OpenGL 1.1 support."); + + functionality_checked = true; + } + + #region --- ITextPrinter Members --- + + public void Prepare(string text, TextureFont font, out TextHandle handle) + { + this.Prepare(text, font, out handle, 0, false, StringAlignment.Near, false); + } + + public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp) + { + this.Prepare(text, font, out handle, width, wordWarp, StringAlignment.Near, false); + } + + public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment) + { + this.Prepare(text, font, out handle, width, wordWarp, alignment, false); + } + + public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft) + { + if (!functionality_checked) + CheckNeededFunctionality(); + + if (text == null) + throw new ArgumentNullException("Parameter cannot be null.", "text"); + + if (text.Length > 4096) + throw new ArgumentOutOfRangeException("text", text.Length, "Text length must be between 1 and 4096 characters"); + + Vector2[] vertices = new Vector2[8 * text.Length]; // Interleaved, vertex, texcoord, vertex, etc... + ushort[] indices = new ushort[6 * text.Length]; + float x_pos = 0, y_pos = 0; + ushort i = 0, index_count = 0, vertex_count = 0, last_break_point = 0; + Box2 rect = new Box2(); + float char_width, char_height, measured_width, measured_height; + int texture; + + // Every character comprises of 4 vertices, forming two triangles. We generate an index array which + // indexes vertices in a triangle-strip fashion. To create a single strip for the whole string, we + // need to add a degenerate triangle (0 height) to connect the last vertex of the previous line with + // the first vertex of the next line. + // This algorithm works for left-to-right scripts. + if (alignment == StringAlignment.Near && !rightToLeft || alignment == StringAlignment.Far && rightToLeft) { - x_pos = vertices[0].X = layoutRect.Left; - y_pos = vertices[0].Y = layoutRect.Top; - vertices[1].X = x_pos; - vertices[1].Y = y_pos - font.Height; - - count = 2; foreach (char c in text) { - // TODO: This code breaks for small widths or for words that do not fit in one width. if (Char.IsSeparator(c)) - last_break_point = i; + last_break_point = index_count; - x_pos += font.MeasureString(text.Substring(i, 1)).Width; - if (x_pos > layoutRect.Width) + if (c == '\n' || c == '\r') { - // Move the last word to the next line. If there is not enough vertical space - // for a new line, finish layout. - + //x_pos = layoutRect.Left; + x_pos = 0; y_pos += font.Height; - if (y_pos > layoutRect.Bottom) - break; - x_pos = layoutRect.Left; - - for (int j = last_break_point; j < i; j++) - { - int current_vertex = 2 * (j + 1); - vertices[current_vertex].X = x_pos; - vertices[current_vertex].Y += font.Height; - current_vertex++; - vertices[current_vertex].X = x_pos; - vertices[current_vertex].Y += font.Height; - - x_pos += font.MeasureString(text.Substring(j, 1)).Width; - } - - x_pos += font.MeasureString(text.Substring(i, 1)).Width; } - vertices[count].X = x_pos; - vertices[count].Y = y_pos; - ++count; - vertices[count].X = x_pos; - vertices[count].Y = y_pos - font.Height; - ++count; + else + { + font.GlyphData(c, out char_width, out char_height, out rect, out texture); + vertices[vertex_count].X = x_pos; // Vertex + vertices[vertex_count++].Y = y_pos; + vertices[vertex_count].X = rect.Left; // Texcoord + vertices[vertex_count++].Y = rect.Top; + vertices[vertex_count].X = x_pos; // Vertex + vertices[vertex_count++].Y = y_pos + char_height; + vertices[vertex_count].X = rect.Left; // Texcoord + vertices[vertex_count++].Y = rect.Bottom; + + vertices[vertex_count].X = x_pos + char_width; // Vertex + vertices[vertex_count++].Y = y_pos + char_height; + vertices[vertex_count].X = rect.Right; // Texcoord + vertices[vertex_count++].Y = rect.Bottom; + vertices[vertex_count].X = x_pos + char_width; // Vertex + vertices[vertex_count++].Y = y_pos; + vertices[vertex_count].X = rect.Right; // Texcoord + vertices[vertex_count++].Y = rect.Top; + + indices[index_count++] = (ushort)(vertex_count - 8); + indices[index_count++] = (ushort)(vertex_count - 6); + indices[index_count++] = (ushort)(vertex_count - 4); + + indices[index_count++] = (ushort)(vertex_count - 4); + indices[index_count++] = (ushort)(vertex_count - 2); + indices[index_count++] = (ushort)(vertex_count - 8); + + + font.MeasureString(text.Substring(i, 1), out measured_width, out measured_height); + x_pos += measured_width; + } ++i; } } @@ -81,9 +155,100 @@ namespace OpenTK.Fonts throw new NotImplementedException("This feature is not yet implemented. Sorry for the inconvenience."); } - int buf; - GL.GenBuffers(1, out buf); + handle = printer.Load(vertices, indices); + handle.font = font; + } + + public void Draw(TextHandle handle) + { + GL.PushAttrib(AttribMask.TextureBit); + GL.PushAttrib(AttribMask.EnableBit); + + GL.Enable(EnableCap.Texture2d); + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + + GL.Enable(EnableCap.Blend); + GL.BindTexture(TextureTarget.Texture2d, handle.font.Texture); + printer.Draw(handle); + + GL.PopAttrib(); + GL.PopAttrib(); + } + + #endregion + } + + public class TextHandle + { + internal TextHandle(int handle) + { + Handle = handle; + } + + public readonly int Handle; + internal TextureFont font; + } + + class VboTextHandle : TextHandle + { + public VboTextHandle(int handle) : base(handle) { } + + internal int vbo_id; // vertex buffer object id. + internal int ebo_id; // index buffer object id. + internal int element_count; // Number of elements in the ebo. + } + + interface IPrinter + { + TextHandle Load(Vector2[] vertices, ushort[] indices); + void Draw(TextHandle handle); + } + + class VboPrinter : IPrinter + { + static int allocated_handles; + static int vector2_size = Marshal.SizeOf(new Vector2()); + + #region --- IPrinter Members --- + + public TextHandle Load(Vector2[] vertices, ushort[] indices) + { + VboTextHandle handle = new VboTextHandle(++allocated_handles); + GL.GenBuffers(1, out handle.vbo_id); + GL.GenBuffers(1, out handle.ebo_id); + + GL.BindBuffer(Version15.ArrayBuffer, handle.vbo_id); + GL.BufferData(Version15.ArrayBuffer, (IntPtr)(vertices.Length * vector2_size), vertices, + Version15.StaticDraw); + + GL.BindBuffer(Version15.ElementArrayBuffer, handle.ebo_id); + GL.BufferData(Version15.ElementArrayBuffer, (IntPtr)(indices.Length * sizeof(ushort)), indices, + Version15.StaticDraw); + handle.element_count = indices.Length; + + return handle; + } + + public void Draw(TextHandle handle) + { + VboTextHandle vbo = (VboTextHandle)handle; + + GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit); + + GL.BindBuffer(Version15.StaticDraw, vbo.vbo_id); + GL.BindBuffer(Version15.ElementArrayBuffer, vbo.ebo_id); + + GL.EnableClientState(EnableCap.VertexArray); + GL.EnableClientState(EnableCap.TextureCoordArray); + + GL.TexCoordPointer(2, TexCoordPointerType.Float, vector2_size, (IntPtr)vector2_size); + GL.VertexPointer(2, VertexPointerType.Float, vector2_size, IntPtr.Zero); + + GL.DrawElements(BeginMode.Triangles, vbo.element_count, All.UnsignedShort, IntPtr.Zero); + + GL.PopClientAttrib(); } #endregion