【问题标题】:Bitmap Performance-Optimization Patterns位图性能优化模式
【发布时间】:2012-03-08 05:10:54
【问题描述】:

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

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

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

模式 0:黑客攻击

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

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

模式 1:缩小图片尺寸

如果您事先知道要调整大小的图像大小,并且准备好丢失细节(颜色)以提高性能,那么这很容易:

  1. 使用 BitmapImage.DecodePixelWidth 减小位图大小
  2. 使用 FormatConvertedBitmap.DestinationFormat 减少颜色信息
  3. 将控件的缩放行为设置 Image.Stretch 设置为 Stretch.None
  4. 将图像的 SetBitmapScalingMode 设置为 LowQuality。
  5. 冻结臭虫

见代码here

模式 2:后台预取

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

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

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

模式 3:绘图上下文

这是由 Microsoft 支持部门的 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 设置为不同的、新的或 null BitmapSources。

编辑This question as posted on the WPF support forum,附有 MS 人员的一些回答。

【问题讨论】:

  • 您预计有多少张图片?我之前制作了一个显示数千张图像网格的应用程序。我们使用了虚拟模式列表框;当用户向下滚动时,图像在后台线程中加载,冻结并设置为图像的源。显然,您对此进行了很多思考……但是,也许更多地了解您想要完成的工作会更好。例如,您遇到过哪些问题使优化成为如此重要的优先事项?
  • @PaulHoenecke 嗨,保罗,谢谢您的回答。当您想要显示大集合中的少量项目时,虚拟集合可以帮助您。在这里,我想同时显示大约 1K 个项目。此外,我不确定是否存在虚拟化网格。最后,模式 1 本质上是虚拟化 - 我将添加 en 编辑。
  • Avi,您的问题看起来不适合所描述的问题。您可能出于某种原因想要提高压缩率,但是您需要确切地知道您正在显示什么样的图像。或者,另一方面,您可能希望给用户所有可用图像的印象,然后您可以定义一些集合并显示缩略图等。但这两件事并不一定相互关联。也许您想要类似相似度指标来定义外观相似的图像集?
  • @artsenay 对不起,我不明白你的 cmets。我想在屏幕上显示很多位图,我想在网格中显示它们,而不是在任意屏幕位置。而且还不够快,所以我希望它看起来更快。
  • @Avi 有什么理由不使用现有算法,例如JPEG 具有低质量设置?当然,这取决于您要压缩的图像,但是如果您有图表之类的图像,则可以尝试使用 PNG 和其中一种编号算法。如果您不知道自己想做什么,那么讨论如何对内存中的二进制数据进行洗牌是没有任何意义的。可能服务器缓存是您最小的问题,因为瓶颈将在客户端或传输中。

标签: wpf performance optimization bitmap design-patterns


【解决方案1】:

我无法在您的帖子中找到具体问题,除了在以下方法中询问 cmets。我不会声称知道以上所有内容,但我会告诉你我在使用 WPF 和 Silverlight 开发高性能 UI 时所知道的。

模式 0:黑客攻击。将所有内容合并为一张图片

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

模式 1:缩小图像尺寸

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

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

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

模式 2:后台预取

这是一种我没有听说过的技术,但它似乎是合理的。您的应用程序的开销是多少?是更新 Image.Source 属性还是创建新图像、镶嵌、执行布局并将信息发送到 GPU?

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

模式 3:绘图上下文

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

模式 4:可写位图

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

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

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

模式 5:缓存位图

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

我会尝试使用 BitmapCache 并查看它的性能。有一些警告,它们是当您更新 UIElement 时,它必须重新创建缓存,因此静态元素的性能将比动态元素好得多。此外,我还没有看到使用它对性能的显着改进,而 WriteableBitmap 样式技术可以提供一个数量级的改进。

模式 6:RenderTargetBitmap

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

最后,我只想说您的要求会将 WPF 推向极限,但是有一些方法可以让它发挥作用。就像我说的,我已经构建了通过使用WriteableBitmap 这个奇妙的解决方法一次在屏幕上渲染数千或数百万个元素的系统。走标准的 WPF 路线会导致性能下降,因此您将不得不做一些奇特的事情来解决这个问题。

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

祝你好运!

【讨论】:

  • 感谢您的回答!一个古老的犹太逾越节故事描述了四个儿子:聪明、邪恶、天真和一个甚至不知道该问什么问题的儿子。我觉得我太缺乏背景了,我什至无法提出正确的问题:“镶嵌”?您是否知道任何描述 WPF 中(2D)图形“管道”的书籍或文章?活动是什么,CPU 做什么,GPU 做什么?
  • 是的,我愿意!看这篇文章:jeremiahmorrill.com/2011/02/14/… 这是相当深奥的东西,不要难过。大多数人会告诉您“只需覆盖 OnRender”以获得性能,或“WPF 使用 GPU”,但这是错误的答案。当您意识到 WPF 在 CPU 方面完成了大部分工作(蹩脚!!)时,您就可以开始优化性能了。
  • 浏览你发给我的线索,我开始觉得 WPF 天生就很慢,应该等待 Direct2D 或 WinRT...
  • @Avi WPF 本来就很慢,是的。然而 WinRT/Xaml 显然也好不到哪里去。 Direct2D 现在可用,您无需等待将其集成到您的应用程序中。但是,如果您想要丰富的数据绑定和快速创建屏幕,您不会在 Direct2D 中找到它!
  • WPF 渲染管道上的那篇文章链接现已失效,请参阅jeremiahmorrill.wordpress.com/2011/02/14/…
猜你喜欢
  • 2022-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
  • 2016-05-14
  • 2011-10-17
  • 2014-01-08
  • 1970-01-01
相关资源
最近更新 更多