【问题标题】:How to draw text or glyphs in WPF really fast at 60 fps?如何以 60 fps 的速度在 WPF 中快速绘制文本或字形?
【发布时间】:2016-01-18 18:25:49
【问题描述】:

我想在 WPF 中创建一个用户控件,它可以使用等宽字体绘制一个 n x m 的字符矩阵。 控件将尽快接受字符串[](目标为 60 fps)并将其绘制在屏幕上。

我需要类似于mplayer ascii playback 的性能。

所有字符都使用相同的等宽字体绘制,但根据一定的规则可能有不同的颜色和背景(类似于VS中的语法高亮)。

我已经在 C# WinForms 中毫无问题地实现了该解决方案,并达到了 60 FPS,但是当我想学习如何在 WPF 中执行此操作时,我只发现了几篇描述 WPF 性能问题和冲突信息的文章和帖子。

那么在这种情况下实现最高性能的最佳方法是什么?

我尝试过的一种天真的方法是:

 /// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    Random rand = new Random();

    public MainWindow()
    {
        InitializeComponent();

        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(1);
        timer.Tick += timer_Tick;
        timer.Start();
    }

    string GenerateRandomString(int length)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++)
        {
            sb.Append(rand.Next(10));
        }
        return sb.ToString();
    }
    void timer_Tick(object sender, EventArgs e)
    {
        myTextBlock.Inlines.Clear();

        for (int i = 0; i < 30; i++)
        {
            var run = new Run();
            run.Text = GenerateRandomString(800);
            run.Foreground = new SolidColorBrush(Color.FromArgb((byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256)));
            run.Background = new SolidColorBrush(Color.FromArgb((byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256)));
            myTextBlock.Inlines.Add(run);
        }

    }
}

问题是:你能比在 WPF 中做得更好吗?

附: 是的,我可以直接使用 DirectX,但这个问题是关于 WPF 而不是 DX。

【问题讨论】:

  • 考虑到 WPF 是硬件加速的 而 WinForms 不是,你很有机会在 WPF 中实现一些可以满足 60 FPS 要求的东西。 WPF 在后台使用 DirectX(例如,可以在 WPF 中使用 HLSL)。除此之外,您的问题可能过于宽泛
  • 当然最好的方法是首先实际构建它。 然后看看它是否符合你的标准。如果没有,然后提问。像这样,实在是太宽泛了。
  • 你怎么知道你有问题?
  • 您的代码示例无助于说明问题。即使暂时忽略一个人是否可以阅读以 60 fps 变化的文本的问题,该代码示例在测量性能方面至少存在两个重大问题:DispatcherTimer 甚至不能可靠地以 60 Hz 触发 Tick 事件;并且没有更仔细地控制内存分配(例如重用单个固定容量StringBuilder,或者甚至更好地预先计​​算要显示的所有字符串),您的测试会被 GC 活动混淆。

标签: c# wpf


【解决方案1】:

在 WPF 中绘制文本最快的方法可能是使用GlyphRun

这是一个示例代码:

MainWindow.xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="300">
    <Image>
        <Image.Source>
            <DrawingImage x:Name="drawingImage"/>
        </Image.Source>
    </Image>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;

namespace WpfApplication
{
    public partial class MainWindow : Window
    {
        Random rand = new Random();

        Stopwatch stopwatch;
        long frameCounter = 0;

        GlyphTypeface glyphTypeface;
        double renderingEmSize, advanceWidth, advanceHeight;
        Point baselineOrigin;

        public MainWindow()
        {
            InitializeComponent();

            new Typeface("Consolas").TryGetGlyphTypeface(out this.glyphTypeface);
            this.renderingEmSize = 10;
            this.advanceWidth = this.glyphTypeface.AdvanceWidths[0] * this.renderingEmSize;
            this.advanceHeight = this.glyphTypeface.Height * this.renderingEmSize;
            this.baselineOrigin = new Point(0, this.glyphTypeface.Baseline * this.renderingEmSize);

            CompositionTarget.Rendering += CompositionTarget_Rendering;

            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(1000);
            timer.Tick += timer_Tick;
            timer.Start();
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (this.stopwatch == null)
                this.stopwatch = Stopwatch.StartNew();

            ++this.frameCounter;

            this.drawingImage.Drawing = this.Render();
        }

        string GenerateRandomString(int length)
        {
            var chars = new char[length];
            for (int i = 0; i < chars.Length; ++i)
                chars[i] = (char)rand.Next('A', 'Z' + 1);

            return new string(chars);
        }

        void timer_Tick(object sender, EventArgs e)
        {
            var seconds = this.stopwatch.Elapsed.TotalSeconds;
            Trace.WriteLine((long)(this.frameCounter / seconds));

            if (seconds > 10)
            {
                this.stopwatch.Restart();
                this.frameCounter = 0;
            }
        }

        private Drawing Render()
        {
            var lines = new string[30];
            for (int i = 0; i < lines.Length; ++i)
                lines[i] = GenerateRandomString(100);

            var drawing = new DrawingGroup();
            using (var drawingContext = drawing.Open())
            {
                // TODO: draw rectangles which represent background.

                // TODO: group of glyphs which has the same color should be drawn together.
                // Following code draws all glyphs in Red color.
                var glyphRun = ConvertTextLinesToGlyphRun(this.glyphTypeface, this.renderingEmSize, this.advanceWidth, this.advanceHeight, this.baselineOrigin, lines);
                drawingContext.DrawGlyphRun(Brushes.Red, glyphRun);
            }

            return drawing;
        }

        static GlyphRun ConvertTextLinesToGlyphRun(GlyphTypeface glyphTypeface, double renderingEmSize, double advanceWidth, double advanceHeight, Point baselineOrigin, string[] lines)
        {
            var glyphIndices = new List<ushort>();
            var advanceWidths = new List<double>();
            var glyphOffsets = new List<Point>();

            var y = baselineOrigin.Y;
            for (int i = 0; i < lines.Length; ++i)
            {
                var line = lines[i];

                var x = baselineOrigin.X;
                for (int j = 0; j < line.Length; ++j)
                {
                    var glyphIndex = glyphTypeface.CharacterToGlyphMap[line[j]];
                    glyphIndices.Add(glyphIndex);
                    advanceWidths.Add(0);
                    glyphOffsets.Add(new Point(x, y));

                    x += advanceWidth;

                }

                y += advanceHeight;
            }

            return new GlyphRun(
                glyphTypeface,
                0,
                false,
                renderingEmSize,
                glyphIndices,
                baselineOrigin,
                advanceWidths,
                glyphOffsets,
                null,
                null,
                null,
                null,
                null);
        }
    }
}

【讨论】:

    【解决方案2】:

    这个答案似乎有点缺陷。 可能的情况:

    1. 每个字符的前进宽度不同
    2. 无需对齐基线,因为一行可能包含 几个字形运行
    3. 由于绝对 y 位置隐藏在运行中,因此在插入行时无法轻易移动

    【讨论】:

    • 你不应该把你的评论放在答案部分!
    猜你喜欢
    • 2015-10-08
    • 1970-01-01
    • 1970-01-01
    • 2018-07-28
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    相关资源
    最近更新 更多