wpf - 位图性能优化模式

标签 wpf performance optimization bitmap design-patterns

我发现了几种优化 WPF 中位图处理的模式。但是,我不明白何时使用每种模式。由于我认为这是一个常见问题,因此我总结了我的理解和猜测并寻求您的帮助。如果可以的话添加图案 ,解释它们有何不同 , 解释他们是否使用 CPU 或 GPU ,和教何时使用每个 如何组合它们 ,这将是一个巨大的帮助!

上下文——图像“网格”场景:

我的应用程序必须显示许多位图图像。图像以行和列的网格状组织显示在屏幕上(不一定是 Grid 或 UniformGrid 类,想想 Window Media Player 的相册 View )。图像可能会在不同的网格单元之间移动。任意单元格的某些图像可能会被其他图像替换。图像应该是可点击的,应该提供一个上下文菜单,应该是可选择的,可拖动的等等。换句话说,“将小 bug 组合成一个大位图”是不适用的,至少不是天真。

模式 0:黑客

将这些小家伙组合成一个位图(如何?绘制上下文?),并将其用作背景。用带有空内容的图像覆盖它,这些图像将处理点击、上下文菜单、事件等。

优点是我们这里只讨论两个位图:当前显示的一个和应该替换它的一个。这应该是非常快的。然而,我多年的经验提出了危险的危险信号。你的评论?

模式 1:缩小图像尺寸

当您事先知道要调整到的图像大小,并且准备好为了性能而丢失细节(颜色)时,这很容易:

  • 使用 BitmapImage.DecodePixelWidth
  • 减小位图大小
  • 使用 FormatConvertedBitmap.DestinationFormat
  • 减少颜色信息
  • 将控件的缩放行为设置 Image.Stretch 设置为 Stretch.None
  • 将图像的 SetBitmapScalingMode 设置为 LowQuality。
  • 卡住 bug

  • 查看代码 here .

    模式二:后台预取

    当您认为可以利用用户注视屏幕上的图像并提前准备要显示的下一个图像时,此模式适用。除了内存开销之外,您的项目的缺点是它必须支持 .Net Framework 4 目标,而不仅仅是客户端配置文件,因此它可能会导致在客户端上进行安装。您自己将不得不忍受异步编程的痛苦。

    在此模式中,您可以准确创建所需数量的 Image 控件。当需要添加、移动或删除位图时,您只需修改 Image 控件的 BitmapSource(s)。 BackgroundWorker 任务负责预取 BitmapSource(s)(可能使用上面的“减少图像大小”模式)并将它们插入 MemoryCache。

    为此,您必须将 BitmapImage 的 CacheOption 设置为 OnLoad,以便将工作卸载到后台工作程序。

    模式 3:绘图上下文

    这是由 Microsoft Support 的 Sheldon Ziao 在 MSDN WPF 论坛 here 上提出的。 .有关 DrawingContext 的说明,请参见 Adam Nathan 的 WPF 4 Unleashed 中的第 494 页第 15 章“2D 图形”。我不能说我理解它。根据答案here ,我认为这会改进几何图形的处理,而不是位图。接下来,我认为这不会支持图像的焦点和事件要求(我的坏处是在论坛上没有更好地解释这些要求)而且,我担心书中的总结句:“注意使用DrawingContext不会改变您在保留模式系统中操作的事实。指定的绘图不会立即发生;这些命令由 WPF 保留,直到需要它们为止。”这意味着一旦我们的偶数处理程序重新启动,我们就不能像“后台预取”那样利用并行性。

    模式 4:可写位图

    MSDN 文档 here将其描述为双缓冲区系统:您的 UI 线程更新缓冲区; WPF 的渲染线程将其移动到视频内存。

    预期用途(请参阅 here )适用于在视频电影(如显示)中变化很大的位图。我不确定,但是这可能会被黑客入侵并与后台预取模式结合并用于网格场景。

    模式 5:缓存位图

    MSDN ( here ) 上的信息不多。在 WPF 论坛存档 (here) 上解释说“BitmapCache API 旨在将您的内容(在硬件中渲染时)缓存在视频内存中,这意味着它会驻留在您的 GPU 上。这可以节省您在将内容绘制到屏幕时重新渲染该内容的成本。”这似乎是个好主意。但是,我不确定有哪些陷阱以及如何使用它。

    模式 6:RenderTargetBitmap

    RenderTargetBitmap 将 Visual 转换为位图。我不确定它在这里是否相关。见 here .

    编辑 :关于 Paul Hoenecke 问题:我写过“我的应用程序必须显示许多位图图像”。我没有提到我需要显示大约 800 张图片 同时 .

    可以阅读我的 SO 问题中涉及的性能问题 WPF Bitmap performanceHow can I make displaying images on WPF more “snappy”?

    我修改了模式 1 的描述以强调不创建或删除图像控件的概念(除非我们想要显示更大或更小的网格)。只有它们的 Sources 被设置为不同的、新的或空的 BitmapSources。

    编辑 :This question as posted on the WPF support forum ,从 MS 人员那里得到了一些答案。

    最佳答案

    除了要求对以下方法发表评论外,我无法在您的帖子中找到具体问题。我不会声称知道以上所有内容,但我会告诉您我在使用 WPF 和 Silverlight 开发高性能 UI 工作了一段时间后所知道的。

    Pattern 0: The Hack. Combining all into one image



    如果可能的话,我会避免这种情况。听起来您想要显示一个包含数千个小图像的大型包装面板。因此,每张图像都是一个缩略图(因为您不能一次显示 1000 幅大图像)。因此,我提倡缓存/调整大小而不是组合。

    Pattern 1: Reduce Image Size



    如果您一次在屏幕上显示 1,000 个图像,请考虑可用的屏幕空间。平均显示器为 1280x1024 像素,或略高于 1.3MPixels。 1000 张图片建议您将获得每张图片的最大尺寸为 1300 像素,或 36*36。假设您的图像大小为 32*32。您绝对应该创建该图像大小的缩略图以在屏幕上呈现,然后单击(或其他操作)显示完整大小的图像。

    不仅要考虑调整大图像大小的渲染开销,还要考虑将大图像发送到 GPU 以调整大小。该数据需要带宽才能发送。大图像可能有几兆字节,而大小为 32*32 的缩略图可能有几千字节。

    如果您需要动态调整大小,那很好,但是您需要尝试创建多个缩略图或即时生成它们。

    Pattern 2: Background pre-fetch



    这是一种我从未听说过的技术,但它似乎是合理的。您的应用程序的开销是多少?它是更新 Image.Source 属性还是创建新图像、分割、执行布局并发送信息以将其呈现给 GPU?

    除了最终渲染之外,以上所有操作都发生在 CPU 上。通过减少 CPU 端的开销并更新源,您可能会有所收获。将此与 WriteableBitmap 作为源结合使用,您可以进一步提高性能(见下文)。

    Pattern 3: Drawing Context



    好的,所有这些都允许您使用“OnPaint”样式语法将保留模式绘图调用排队,这与旧的 GDI OnPaint 完全不同。根据我的经验,OnRender 不会提高性能,但它确实允许对绘制的内容和时间进行细粒度的灵活性。 OnRender 为您提供了一个具有 DrawImage 函数的上下文,允许将 BitmapSource 绘制到渲染管道,而无需 Image 控件。这很好,因为它消除了一些开销,但是会引入类似于 Pattern0 中看到的问题(您将丢失布局并且必须计算所有图像的位置)。如果你这样做,你不妨调用模式 0,我不建议这样做。

    Pattern 4: Writeable Bitmaps



    WriteableBitmaps 是 WPF 中很少使用且非常强大的子系统。我使用它们来创建一个能够实时渲染大量数据的图表组件,效果非常好。我建议查看 WriteableBitmapEx codeplex 项目 披露,我曾经为此做出过贡献看看你是否可以将它与其他模式结合起来。特别是 Blit 函数,它可以让您将缓存的位图写入图像上的位图源。

    例如,一个好的技术可能是模式 1 + 2 + 4。

    您可以在屏幕上的网格控件中的设定位置放置一个包含 N 个图像控件的网格。这些中的每一个都是静态的,不会滚动出 View ,因此不会进行创建/删除。现在,在此之上,调整图像大小并写入被设置为每个图像的 Source 属性的 WriteableBitmap。滚动时,获取下一个/上一个缩略图并使用 WriteableBitmapEx.Blit 更新源。哇!虚拟化、缓存、多线程成像优势。

    Pattern 5: Cached Bitmap



    正如我上面讨论的那样,这是微软尝试做 1+2+4 的尝试。它试图做的是在布局(CPU 端)、曲面分割(CPU 端)、将保留模式渲染指令发送到 GPU(CPU 端)和渲染(GPU 端)之后,它会缓存渲染元素的光栅图像,这是重新用于下一个渲染 channel 。是的,关于 WPF 的一个鲜为人知的事实是,出色的 GPU 驱动引擎非常慢,因为它在 CPU 上完成大部分工作:P

    我会用 BitmapCache 进行试验,看看它的表现如何。有一些警告,当您更新 UIElement 时,它必须重新创建缓存,因此静态元素的性能将远远优于动态元素。此外,我没有看到使用它的性能有显着提高,而 WriteableBitmap 样式技术可以提供一个数量级的改进。

    Pattern 6: RenderTargetBitmap



    最后一项技术允许您将 UIElement 渲染为位图 - 您知道这一点 - 但有趣的是它可以执行穷人缩略图生成器(或调整大小)。例如,使用全尺寸图像的 BitmapSource 设置图像。现在将图像控件的大小设置为 32*32 并渲染为位图。瞧!您将 Bi​​tmapSource 缩略图与一些交换(模式 2)和/或可写位图结合使用。

    好的,最后,只是想说您的要求会将 WPF 推向极限,但是有一些方法可以让它执行。就像我说的,我已经构建了一个系统,通过使用 WriteableBitmap 的出色解决方法,一次在屏幕上呈现数千或数百万个元素。沿着标准的 WPF 路线走下去会导致性能下降,所以你必须做一些奇特的事情来解决这个问题。

    正如我所说,我的建议是 1+2+4。您必须调整缩略图的大小,我对此毫不怀疑。拥有静态图像控件网格和更新源的想法非常好。使用 WriteableBitmap(特别是 WriteableBitmapEx blit 函数)更新源的想法也是值得探索的。

    祝你好运!

    关于wpf - 位图性能优化模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9317460/

    相关文章:

    c# - 使用 OR 而不是 AND 的 MultiDataTrigger

    c# - 使用可选参数初始化 WPF 窗口

    c++ - 使用有限的RAM运行C++程序

    c - 读取大数据时有没有比 fscanf 更快的方法

    assembly - 编写跨步 x86 基准测试

    c# - 如何在 View 模型中的 View 上使用路由命令

    java - 是什么让这个 Clojure 函数变慢了?

    android - 我应该用 Fragments 替换 Android Activity 吗?

    python - 计算连接到优化器中同一神经元的张量值子集的值

    c# - MVVM:ViewModel 和业务逻辑连接