位图性能优化模式
我发现了几种用于优化 WPF 中位图处理的模式。然而,我不明白何时使用每种模式。由于我认为这是一个常见问题,所以我总结了我的理解和我的猜测,并寻求您的帮助。如果您可以添加模式,请解释它们有何不同,解释它们是否使用CPU或GPU,并教导何时使用每个以及如何组合它们,这将是一个巨大的帮助!
上下文 - 图像“网格”场景:
我的应用程序必须显示许多位图图像。图像以类似行和列的网格组织形式显示在屏幕上(不一定是 Grid 或 UniformGrid 类,可以想象 Window Media Player 的相册视图)。图像可能在不同的网格单元之间移动。任意单元格处的某些图像可能会被其他图像替换。图像应该是可点击的,应该提供上下文菜单,应该是可选择的,可拖动的等等。换句话说,“将小虫子组合成一个大位图”是不适用的,至少不是天真的。
模式 0:黑客
将这些小虫子组合成一个位图(如何?绘制上下文?),并将其用作背景。用带有空内容的图像覆盖它,以处理点击、上下文菜单、事件等。
优点是我们在这里只讨论两个位图:当前显示的一个和应该替换它的一个。这应该是非常快的。然而,我多年的经验却提出了危险的危险信号。你的意见?
模式 1:减小图像尺寸
当您提前知道要调整大小的图像尺寸,并且准备为了性能而丢失细节(颜色)时,这是一个显而易见的选择:
- 减小位图尺寸使用 BitmapImage.DecodePixelWidth 使用
- FormatConvertedBitmap.DestinationFormat 减少颜色信息
- 将控件的缩放行为设置 Image.Stretch 设置为 Stretch.None
- 将图像的 SetBitmapScalingMode 设置为 LowQuality。
- 冻结 bugger
请参阅此处的代码。
模式 2:后台预取
当您认为可以利用用户注视屏幕上的图像的机会,并提前准备下一个要显示的图像时,可以使用此模式。除了内存开销之外,您的项目的缺点是它必须支持 .Net Framework 4 目标而不仅仅是客户端配置文件,因此可能需要在客户端上进行安装。您自己将不得不承受异步编程的痛苦。
在此模式中,您可以创建所需数量的图像控件。当需要添加、移动或删除位图时,您只需修改图像控件的 BitmapSource。 BackgroundWorker 任务负责预取 BitmapSource(可能使用上面的“减小图像大小”模式)并将它们插入到 MemoryCache 中。
为此,您必须将 BitmapImage 的 CacheOption 设置为 OnLoad,以便将工作卸载到后台工作人员。
模式 3:绘制上下文
这是由 Microsoft 支持部门的 Sheldon Ziao 在 MSDN WPF 论坛上建议的 此处。有关 DrawingContext 的说明,请参阅 Adam Nathan 的 WPF 4 Unleashed 中第 494 页第 15 章“2D 图形”。我不能说我理解它。根据此处的答案,我认为这会改善几何图形的处理,而不是位图。接下来,我认为这不会支持图像的焦点和事件要求(我很抱歉没有在论坛上更好地解释这些要求)此外,我对本书的总结句子感到担心:“请注意,使用 DrawingContext不会改变您在保留模式系统中运行的事实。指定的绘图不会立即发生;这些命令由 WPF 保留,直到需要为止。”这意味着一旦我们的偶数处理程序重新启动,我们就无法利用“后台预取”中的并行性。
模式 4:可写位图
MSDN 文档 这里将其描述为双缓冲区系统:您的UI线程更新缓冲区; WPF 的渲染线程将其移动到视频内存。
预期用途(请参阅此处)适用于位图这在视频电影等显示中发生了很大的变化。我不确定,但可能这可以被黑客攻击并与后台预取模式结合并在网格场景中使用。
模式 5:缓存位图
MSDN 上没有太多信息 (此处)。在 WPF 论坛存档上(此处< /a>)它解释说“BitmapCache API 旨在将您的内容(在硬件中渲染时)缓存在视频内存中,这意味着它驻留在您的 GPU 上。这可以节省您在将内容绘制到屏幕上时重新渲染内容的成本。”这似乎是个好主意。但是,我不确定有哪些陷阱以及如何使用它。
模式 6:RenderTargetBitmap
RenderTargetBitmap 将视觉对象转换为位图。我不确定它在这里是否相关。请参阅此处。
编辑:关于Paul Hoenecke的问题:我写过“我的应用程序必须显示许多位图图像”。我没有提到我需要同时显示大约 800 张图像。
人们可以阅读我的 SO 问题 WPF 位图性能 和 如何在 WPF 上显示图像更多“snappy”?
我修改了模式 1 的描述,以突出图像控件不会创建或删除的概念(除非我们想要显示更大或更小的网格)。仅将它们的源设置为不同的、新的或空的 BitmapSource。
编辑:此问题已发布在 WPF 支持论坛上,其中有 MS 人员的一些回答。
I found several patterns for optimizing Bitmaps handling in WPF. I do not, however, understand when to use each patterns. As I think this is a common problem, I summarized what I understand and what I guess and ask for your help. If you can add patterns, explain how they differ, explain if they use the CPU or the GPU, and teach when to use each and how to combine them, it’d be a tremendous help!
Context – the Images "Grid" Scenario:
My application has to display many bitmap images. The images are displayed on the screen in a rows-and-columns grid-like organization (not necessarily the Grid or UniformGrid classes, think Window Media Player’s Album view). Images might move between different grid cells. Some images at arbitrary cells may be replaced by others. Images should be clickable, should provide a context menu, should be selectable, drag-able etc. In other words, “combine the little buggers into one big bitmap” is not applicable, at least not naively.
Pattern 0: The Hack
Do combine the little buggers into a bitmap (how? drawing context?), and use this as the background. Overlay this with images with empty content that will handle the hits, context menus, events etc.
The advantage is that we're only speaking about two bitmaps here: The currently displayed one and the one that should replace it. This should be really fast. However, my years of experience raise the red flag of danger. Your comments?
Pattern 1: Reduce Image Size
This is a no-brainer when you know in advance the image size to resize to, and when you’re prepared to lose details (color) for performance:
- Reduce the bitmap size using BitmapImage.DecodePixelWidth
- Reduce the color information using FormatConvertedBitmap.DestinationFormat
- Set the control’s scaling behavior setting Image.Stretch to Stretch.None
- Set the SetBitmapScalingMode for the image to LowQuality.
- Freeze the bugger
See code here.
Pattern 2: Background pre-fetch
This pattern is applicable when you think you can take advantage of the user gazing at the images on the screen, and prepare ahead the next images to display. The cons for your project, in addition to the memory overhead, is that it has to support the .Net Framework 4 target and not just the client profile, so it might incur an installation on the client’s. You yourself will have to suffer the async programming pain.
In this pattern you create exactly the required number of Image controls. When bitmaps need to be added, moved or deleted you only modify the Image controls' BitmapSource(s). A BackgroundWorker task is responsible for pre-fetching the BitmapSource(s) (possibly using the “Reduce Image Size” pattern above) and inserting them into MemoryCache.
For this to work you have to set the BitmapImage’s CacheOption to OnLoad, so that the work is off-loaded to the background worker.
Pattern 3: Drawing Context
This was suggested by Sheldon Ziao from Microsoft Support on the MSDN WPF forum here. See page 494, Chapter 15 “2D Graphics” in Adam Nathan’s WPF 4 Unleashed for a description of DrawingContext. I can’t say I understand it. According to the answer here, I would assume this would improve handling of Geometry drawings, not bitmaps. Next, I don’t think this will support the focus and events requirements for the images (my bad for not explaining the requirements better at the forum) Moreover, I’m worried by the book’s summary sentence: “Note that the use of DrawingContext doesn’t change the fact that you’re operating within a retained-mode system. The specified drawing doesn’t happen immediately; the commands are persisted by WPF until they are needed.” This means that once our even handler re we can’t take advantage of parallelism as in “Background pre-fetch”.
Pattern 4: Writeable Bitmaps
The MSDN documentation here describes it as a dual buffer system: Your UI thread updates the buffer; the WPF’s render thread moves this to video memory.
The intended usage (see here) is for bitmaps that change a lot such in a video movie like display. I’m not sure, but possible this could be hacked and combined with the Background Pre-fetch pattern and used in the grid scenario.
Pattern 5: Cached Bitmap
Not much info on the MSDN (here). On the WPF forum archive (here) it’s explained that “The BitmapCache API is designed to cache your content (when rendering in hardware) in video memory, meaning it stays resident on your GPU. This saves you the cost of re-rendering that content when drawing it to the screen.” This seems like a great idea. I’m not sure, however, what are the pitfalls and how to use it.
Pattern 6: RenderTargetBitmap
The RenderTargetBitmap converts a Visual to a bitmap. I’m not sure if it’s relevant here. See here.
Edit: Regarding Paul Hoenecke question: I've written that "My application has to display many bitmap iages". I failed to mentions that I need to display about 800 images concurrently.
One can read about the performance issues involved at my SO questions WPF Bitmap performance and How can I make displaying images on WPF more “snappy”?
I've modified the description of pattern 1 to highlight the concept that the image controls aren't created or deleted (unless we want to display a larger or smaller grid). Only their Sources are set to different, new or null BitmapSources.
Edit: This question as posted on the WPF support forum, with some answers from MS personnel.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
除了询问对以下方法的评论之外,我无法在您的帖子中找到具体问题。我不会声称了解上述所有内容,但我会告诉您我在使用 WPF 和 Silverlight 开发高性能 UI 一段时间后所知道的内容。
我会避免这种情况。听起来您想显示一个由数千个小图像组成的大包裹面板。因此,每个图像都是缩略图(因为您无法一次显示 1000 个大图像)。因此,我主张缓存/调整大小而不是组合。
如果您要同时在屏幕上显示 1,000 个图像,请考虑可用的屏幕空间。显示器的平均像素为 1280x1024 像素,即略高于 1.3M 像素。 1000 张图像建议您获得的每张图像的最大尺寸为 1300 像素,即 36*36。假设您的图像尺寸为 32*32。您绝对应该创建该图像尺寸的缩略图以在屏幕上渲染,然后单击(或其他操作)显示完整尺寸的图像。
不仅要考虑调整大图像大小的渲染开销,还要考虑将大图像发送到 GPU 来调整大小的渲染开销。该数据需要带宽来发送。大图像可能有几兆字节,而大小为 32*32 的缩略图可能有几千字节。
如果您需要动态调整大小,没问题,但您需要尝试创建多个缩略图或动态生成它们。
这是一种我没有听说过的技术,但它看起来似乎很有道理。您的应用程序的开销是多少?是更新 Image.Source 属性还是创建新图像、细分、执行布局并将信息发送到 GPU?
除最终渲染外,上述所有操作均发生在 CPU 上。通过减少 CPU 方面的开销并更新源代码,您可能会有所收获。将此与 WriteableBitmap 作为源相结合,您可以进一步获得性能改进(见下文)。
好吧,这一切所做的只是允许您使用“OnPaint”样式语法对保留模式绘图调用进行排队,该语法与旧的 GDI OnPaint 完全不同。根据我的经验,OnRender 不会提高性能,但它确实允许在绘制内容和绘制时间方面提供细粒度的灵活性。 OnRender 为您提供了一个上下文,该上下文具有 DrawImage 函数,允许将 BitmapSource 绘制到渲染管道,而无需 Image 控件。这很好,因为它消除了一些开销,但是引入了与 Pattern0 中类似的问题(您将丢失布局并且必须计算所有图像的位置)。如果您这样做,您还可以调用模式 0,我建议不要这样做。
WriteableBitmap 是 WPF 中很少使用但非常强大的子系统。我使用它们创建了一个能够实时渲染大量数据的图表组件,效果非常好。我建议查看 WriteableBitmapEx codeplex 项目Disclosure,我曾经对此做出过贡献,看看是否可以将其与其他模式结合起来。具体来说,Blit 函数可以让您将缓存的位图写入图像上的位图源。
例如,一个好的技术可能是模式 1 + 2 + 4。
您可以在屏幕上的网格控件的设定位置上有一个由 N 个图像控件组成的网格。其中每一个都是静态的,不会滚动到视图之外,因此不会发生创建/删除操作。现在,在此之上,调整图像大小并写入 WriteableBitmap,该值被设置为每个图像上的 Source 属性。当您滚动时,获取下一个/上一个缩略图并使用 WriteableBitmapEx.Blit 更新源。噗!虚拟化、缓存、多线程成像优势。
这是微软尝试执行我上面讨论的 1+2+4 的方法。它尝试做的是在布局(CPU 端)、曲面细分(CPU 端)、将保留模式渲染指令发送到 GPU(CPU 端)和渲染(GPU 端)之后,它会缓存渲染元素的光栅图像,该图像会重新生成。在下一个渲染过程中使用。是的,关于 WPF 的一个鲜为人知的事实是,由 GPU 驱动的引擎非常慢,因为它的大部分工作都是在 CPU 上完成的:P
我会尝试使用 BitmapCache 并看看它的性能如何。有一些警告,当您更新 UIElement 时,它必须重新创建缓存,以便静态元素的性能比动态元素好得多。此外,我还没有看到使用此方法可以显着提高性能,而 WriteableBitmap 样式技术可以提供数量级的改进。
最后一项技术可让您将 UIElement 渲染为位图 - 您知道这一点 - 但有趣的是,这可以执行穷人的缩略图生成器(或调整大小)。例如,使用全尺寸图像的 BitmapSource 设置图像。现在将 Image 控件的大小设置为 32*32 并渲染为位图。瞧!您可以将 BitmapSource 缩略图与某些交换(模式 2)和/或可写位图结合使用。
好吧,最后,只是想说您的要求会将 WPF 推向极限,但是有一些方法可以让它执行。就像我说的,我已经构建了系统,通过使用 WriteableBitmap 这个奇妙的解决方法,可以在屏幕上一次渲染数千或数百万个元素。采用标准 WPF 路线将导致性能地狱,因此您必须采取一些奇特的措施来解决此问题。
正如我所说,我的建议是 1+2+4。您必须调整缩略图的大小,对此我毫不怀疑。拥有图像控件的静态网格并更新源的想法非常好。使用 WriteableBitmap(特别是 WriteableBitmapEx blit 函数)来更新源的想法也是值得探索的。
祝你好运!
I'm unable to find a specific question in your post, other than asking for comments on the approaches below. I won't claim to know everything above but I'll tell you what I do know having worked for a while developing high-performance UIs using WPF and Silverlight.
I'd avoid this if possible. It sounds like you want to display a large wrap-panel of thousands of small images. Each image is therefore a thumbnail (as you cannot display 1000s of large images at once). As a result, I'd advocate caching/resize over combination.
If you are displaying 1,000 images on screen at once, consider the available screen real-estate. The average monitor is 1280x1024 pixels, or just over 1.3MPixels. 1000 images suggests you will get a maximum size of 1300 pixels per image, or 36*36. Lets say your images are 32*32 in size. You should definitely be creating a thumbnail of that image size to render on screen, then on click (or other action) show the full size image.
Also consider not only the render overhead of resizing a large image, but of sending a large image to the GPU to resize. That data requires bandwidth to send. A large image can be several megabytes whereas a thumbnail of size 32*32 could be a few kilobytes.
If you require dynamic sizing, fine, but you'll need to experiment with creating multiple thumbnails or generating them on the fly.
This is a technique I've not heard of, however it seems plausible. What is the overhead in your application? is it updating the Image.Source property or creating a new Image, tessellating, performing Layout and sending the information to render it to the GPU?
All the above occur on the CPU except for the final render. By reducing the overhead on the CPU side and updating the source you might be on to something. Combine this with WriteableBitmap as a source and you could further gain a performance improvement (see below).
Ok, all this does is allow you to queue up retained mode drawing calls using an "OnPaint" style syntax which is nothing like the old GDI OnPaint. In my experience OnRender doesn't improve performance, but it does allow for fine grained flexibility over what is drawn and when. OnRender provides you with a context, which has a DrawImage function, allowing a BitmapSource to be drawn to the rendering pipeline without the need for an Image control. This is good as it removes some overhead, however introduces problems similar to those seen in Pattern0 (you will lose layout and have to compute position of all your images). If you do this, you might as well invoke Pattern 0, which I advised against.
WriteableBitmaps are a little used and extraordinarily powerful subsystem within WPF. I use them to great effect to create a charting component capable of rendering large amounts of data in real-time. I would suggest checking out the WriteableBitmapEx codeplex project Disclosure, I have contributed to this once and seeing if you can combine it with other patterns. Specifically the Blit function which would let you write a cached bitmap to a bitmap source on an image.
For instance, a good technique might be Pattern 1 + 2 + 4.
You could have a grid of N Image controls on the screen at set locations in a grid control. Each of these is static and doesn't get scrolled out of view so there are no creations/deletions going on. Now, on top of this, resize your image and write to a WriteableBitmap which is set as the Source property on each image. As you scroll, get the next/previous thumbnails and update the sources using WriteableBitmapEx.Blit. Pow! virtualized, cached, multi-threaded imaging goodness.
This is an attempt by microsoft to do 1+2+4 as I discussed above. What it tries to do is after layout (CPU side), tessellation (CPU Side), sending retained mode render instructions to the GPU (CPU side) and rendering (GPU side) it caches a raster image of the rendered element which is re-used on the next rendering pass. Yes a little known fact about WPF is that wonderful GPU powered engine is terribly slow as it does most of its work on the CPU :P
I would experiment with BitmapCache and see how it performs. There are caveats, and they are that when you update your UIElement it has to recreate the cache so static elements will perform far better than dynamic. Also I've not seen a significant improvement in performance from using this whereas WriteableBitmap style techniques can give an order of magnitude improvement.
This final technique lets you render a UIElement to a bitmap - you know that - but what is interesting is this can perform a poor-mans thumbnail generator (or resize). For instance, set an Image with BitmapSource of your full size image. Now set the size of the Image control to 32*32 and render to bitmap. Voila! You have your BitmapSource thumbnail to use in conjunction with some swapping (Pattern 2) and/or writeable bitmaps.
Ok finally, just wanted to say the requirement you have will push WPF to its limits, however there are ways to get it to perform. Like I said, I have build systems which rendered thousands or millions of elements on the screen at once by using the wonderful workaround that is WriteableBitmap. Going down the standard WPF route will result in performance hell so you will have to do something exotic to solve this.
As I said my recommendation is 1+2+4. You must resize a thumbnail, of that I have no doubt. The idea of having a static grid of Image controls and updating the sources is very good. The idea of using WriteableBitmap (specifically WriteableBitmapEx blit function) to update the sources is also one worth exploring.
Good luck!