/// <summary>Sweeps all marked entries as part of mark-and-sweep garbage collection.</summary> /// <remarks>This method should be called at the end of each frame.</remarks> public void RemoveUnusedEntries() { bool unusedEntriesFound; do { unusedEntriesFound = false; foreach (KeyValuePair <string, TextCacheEntry> dictionaryEntry in entries) { TextCacheEntry entry = dictionaryEntry.Value; if (entry.Unused) { for (int i = 0; i < entry.TexturePoolEntries.Length; ++i) { TexturePoolEntry poolEntry = entry.TexturePoolEntries[i]; poolEntry.StripeAllocated[entry.StripeIndexes[i]] = false; if (!poolEntry.Used) { poolEntry.Texture.Dispose(); texturePool.Remove(poolEntry); } } entries.Remove(dictionaryEntry.Key); unusedEntriesFound = true; break; // the iterator has been invalidated, so we need a new foreach loop } } } while(unusedEntriesFound); }
/// <summary>Returns a cached text resource, or creates it if it doesn't exist.</summary> /// <param name="text">Text to render.</param> /// <returns>The cached text resource as an array of text fragments.</returns> public DXCachedTextFragment[] GetText(string text, out int textWidthInPixels) { TextCacheEntry entry = getOrCreateEntry(text); textWidthInPixels = entry.TextWidthInPixels; return(entry.TextFragments); }
/// <summary>Returns a cached text resource, or creates it if it doesn't exist.</summary> /// <param name="text">Text to render.</param> /// <returns>The cached text resource as an instance of TextCacheEntry.</returns> private TextCacheEntry getOrCreateEntry(string text) { TextCacheEntry entry; if (!entries.TryGetValue(text, out entry)) { // The text was not found in the cache -> create it SizeF textSize; using (Bitmap bitmap = new Bitmap(1, 1, PixelFormat.Format24bppRgb)) { using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap)) { textSize = graphics.MeasureString(text, font); } } entry = new TextCacheEntry(); entries.Add(text, entry); entry.Unused = false; entry.TextWidthInPixels = 2 + (int)Math.Ceiling(textSize.Width); int fragmentsCount = (entry.TextWidthInPixels + 255) / 256; entry.TextFragments = new DXCachedTextFragment[fragmentsCount]; entry.TexturePoolEntries = new TexturePoolEntry[fragmentsCount]; entry.StripeIndexes = new int[fragmentsCount]; for (int i = 0; i < fragmentsCount; ++i) { allocateTextureFragment(out entry.TexturePoolEntries[i], out entry.StripeIndexes[i], out entry.TextFragments[i]); } renderTextToTextures(entry, text); } entry.Unused = false; return(entry); }
// ====================================================================================== // Text drawing // ====================================================================================== /// <summary> /// Draws a text string. Returns the bounds of the drawn text. /// </summary> /// <param name="text">The text to draw.</param> /// <param name="position">The position where the text will be drawn.</param> /// <param name="color">The color of the text.</param> /// <param name="font">The font to use to draw the text.</param> /// <param name="alignment">The alignment of the text relative to the position. If unspecified the text will be drawn left-aligned.</param> /// <param name="measureOnly">If true the text will only be measured and not actually drawn to the screen.</param> public static Bounds2 DrawString(string text, Vector2 position, Color color, Font font, TextAlignment alignment = TextAlignment.Left, bool measureOnly = false) { // Every time we draw a string we have to render it into a texture, which isn't the fastest thing in the world. // To make this faster we keep a cache of recently rendered strings and reuse them when possible. Tuple <Font, string> textCacheKey = Tuple.Create(font, text); TextCacheEntry entry; if (TextCache.TryGetValue(textCacheKey, out entry)) { // We found the text in the cache, so use it and reset its age so that it doesn't get freed this frame: entry.Age = 0; } else { // We were unable to find the text in the cache, so render it to a texture and cache it for later: SDL.SDL_Color white = new SDL.SDL_Color() { r = 255, g = 255, b = 255, a = 255 }; IntPtr surface = SDL_ttf.TTF_RenderText_Blended(font.Handle, text, white); IntPtr handle = SDL.SDL_CreateTextureFromSurface(Renderer, surface); SDL.SDL_FreeSurface(surface); entry = new TextCacheEntry(handle); TextCache[textCacheKey] = entry; } // Query the texture dimensions: uint format; int access, width, height; SDL.SDL_QueryTexture(entry.Handle, out format, out access, out width, out height); // Apply text alignment relative to the draw position: if (alignment == TextAlignment.Center) { position.X -= width / 2; } else if (alignment == TextAlignment.Right) { position.X -= width; } // If we're not only measuring the text, draw it: if (!measureOnly) { Texture texture = new Texture(entry.Handle, width, height); DrawTexture(texture, position, color); } // Return the bounds of the text: return(new Bounds2(position, new Vector2(width, height))); }
/// <summary>Creates a new text resource.</summary> /// <param name="cacheEntry">An instance of TextCacheEntry.</param> /// <param name="text">Text.</param> /// <remarks>The text is white, surrounded by a one pixel wide black edge.</remarks> private unsafe void renderTextToTextures(TextCacheEntry cacheEntry, string text) { using (Bitmap bitmap = new Bitmap(cacheEntry.TextWidthInPixels, textHeight, PixelFormat.Format24bppRgb)) { using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap)) { graphics.FillRectangle(new SolidBrush(Color.Black), 0, 0, bitmap.Width, textHeight); graphics.DrawString(text, font, new SolidBrush(Color.White), 1.0f, 2.0f - descentInPixels); } BitmapData bitmapData = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int width = bitmapData.Width; int stride = bitmapData.Stride; // build transparency mask (using a 3x3 filter grid) ushort[] transparencyMask = new ushort[(width + 2) * (textHeight + 2)]; fixed(ushort *mask2 = transparencyMask) { { ushort *firstLine = mask2; ushort *secondLine = firstLine + (width + 2); ushort *thirdLine = secondLine + (width + 2); byte *source = (byte *)bitmapData.Scan0; for (int y = 0; y < textHeight; ++y) { for (int x = 0; x < width; ++x) { ushort blue = *source; *(firstLine + 0) += blue; *(firstLine + 1) += blue; *(firstLine + 2) += blue; *(secondLine + 0) += blue; *(secondLine + 1) += blue; *(secondLine + 2) += blue; *(thirdLine + 0) += blue; *(thirdLine + 1) += blue; *(thirdLine + 2) = blue; // *(thirdLine+2) == 0 -> no need to add ++firstLine; ++secondLine; ++thirdLine; source += 3; } firstLine += 2; secondLine += 2; thirdLine += 2; source += stride - width * 3; } } // copy bitmap to textures int fragmentsCount = cacheEntry.TexturePoolEntries.Length; for (int i = 0; i < fragmentsCount; ++i) { Texture texture = cacheEntry.TexturePoolEntries[i].Texture; int stripeIndex = cacheEntry.StripeIndexes[i]; int fragmentOrigin = i * 256; int fragmentWidth = (i < fragmentsCount - 1 ? 256 : width % 256); int texturePitch; byte *textureBits = (byte *)texture.LockRectangle(0, LockFlags.None, out texturePitch).InternalData.ToPointer(); byte * source = (byte *)bitmapData.Scan0 + fragmentOrigin * 3; ushort *mask = mask2 + (width + 3) + fragmentOrigin; byte *dest = textureBits + (stripeIndex * textHeight) * texturePitch; for (int y = 0; y < textHeight; ++y) { for (int x = 0; x < fragmentWidth; ++x) { *(dest + 0) = *(source + 0); *(dest + 1) = *(source + 1); *(dest + 2) = *(source + 2); *(dest + 3) = (*mask > 0xFF ? (byte)0xFF : (byte)*mask); dest += 4; source += 3; ++mask; } for (int x = fragmentWidth; x < 256; ++x) { *(uint *)dest = 0x00000000; dest += 4; } dest += texturePitch - 256 * 4; source += stride - fragmentWidth * 3; mask += width + 2 - fragmentWidth; } texture.UnlockRectangle(0); } } } }