【翻译】关于 WPF 透明窗口的内存占用

【翻译】关于 WPF 透明窗口的内存占用

翻译自己的文章才是最骚的。。。 Origin Post


要实现一个透明的 WPF 窗口?
多么简单的一个任务啊!只要设置 AllowTransparency 和 WindowStyle,你可以在毫秒间完成这个任务。

AllowTransparency="True" WindowStyle="None" Background="Transparent"

正确吧?当然。
但是(你懂得),打开你的任务管理器看看,简单的任务通常会带来大量的内存占用,特别是4K分辨率的透明窗口。100+ MB的内存被浪费了,就只为了显示一个空白的透明窗口!这是不可接受的。一年前,如果你说:”谁关心内存呀,现在的内存条太便宜了”,你可能是对的。但是查查这一年内存条的价格走向,它们现在贵上天了。

WPF 透明窗口的有趣小真相

  • 内存占用随着窗口尺寸增大而增加
  • Win32 窗口没有这样的问题

等等,什么?窗口越大,内存消耗的更多?嗯。。。这看起来很熟悉嘛,就像一个Bitmap。知道现在,我们并不知道 WPF 是如何处理透明窗口的,但是这种症状显示它就好像直接将整个桌面作为一个位图,然后窗口用这张位图的重叠部分作为其背景来更新自己,让它看起来是“透明”的。多么聪明的做法呀。。。
在 WPF 刚刚发布的那些日子里,低分辨率的计算机屏幕占据主流位置,即使在今天,大多数的笔记本电脑依然带着一块1366*768分辨率的屏幕被推向市场(离谱吧)。让我们唾弃那些OEM厂商讲的毫无根据的废话并且思考一下运行在高分辨率下的程序的情况。

内存并不是免费的,不要浪费之

很显然,浪费100+MB的内存来显示一个4K的透明窗口是不可接受的,特别是和 Win32 窗口只占用10+MB的内存进行比较时。这差距让 WPF 看起来蠢透了。
抱怨已经够多了,想想对此我们能做什么呢?我可不想用C++和GDI将我的UI代码重写一遍,这太没效率并且也跟不上时代,况且,没人会为此“区区小事”去放弃他们漂亮、易于维护的Xaml UI代码。

使用 Win32 承载 WPF 内容

好吧,确实,没人愿意为了区区90MB内存去重写它们的UI。与使用C++重写UI所耗费的精力相比,这个内存的占用差距看起来是可以接受的(#笑脸)。但是请记住,我们一如既往的可以在 Win32 窗口中承载 WPF 的内容。
例如,我们想创建一个全屏、半透明背景带着非透明内容的对话框。为了规避 WPF 透明窗口的内存问题,我们可以使用 Win32 创建一个半透明的窗口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD Flags1 = WS_EX_LAYERED;
DWORD Flags2 = WS_POPUP;

HWND hWnd = CreateWindowEx(Flags1,szWindowClass, szTitle, Flags2,
CW_USEDEFAULT, 0, 3840,2160, nullptr, nullptr, hInstance, nullptr);

SetLayeredWindowAttributes(hWnd, RRR, (BYTE)125, LWA_ALPHA);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

case WM_ERASEBKGND:
RECT rect;
GetClientRect(hWnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(0, 0, 0)));
break;

通过启用 C++/CLI,我们可以直接访问 WPF 内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);

UIElement^ page = gcnew ManagedContent::WPFContent();
source->RootVisual = page;
return (HWND)source->Handle.ToPointer();
}
}

最后

1
2
//managed content
ManagedCode::GetHwnd(hWnd, 0, 0, 200, 200);

由于 WPF 和 GDI 背后的技术不尽相同,还有更多的工作需要做来解决不可避免的透明通道问题,但是,为了方便,你始终可以使用 Popup 来实现你的目标。


Macbook Pro 2016 的键盘真垃圾。。。。。。

关于 WPF 透明窗口的内存占用

关于 WPF 透明窗口的内存占用

要实现一个透明的 WPF 窗口?
What an easy task! By setting AllowTransparency and WindowStyle, you could finish it in seconds.

AllowTransparency="True" WindowStyle="None" Background="Transparent"

Correct? Of course.

However (you know this is coming), look at your task manager, easy task comes with large memory consumption, especially for 4K transparent window. 100+ MB ram are wasted for just showing an empty, transparent window! That’s unacceptable. A year ago, you might be right saying “Who cares about RAM, they are cheap as hell”, but check out the price they’ve grown over this year, they are expensive as hell now.

Fun fact of WPF transparent window

  • RAM usage increase as window size enlarge
  • Win32 window has no such problem

Wait, what? The larger the window is, the more RAM it consumes? Hmmmmm… this looks familiar, just like a Bitmap. For now, we don’t know how WPF handles transparent window, but the symptom shows that it’s like using the whole screen as a bitmap and the window updating itself with portion of that bitmap, making it “transparent”. What a smart move…
Back in the days when WPF was first released, low screen resolution was the main stream. Even today, most laptops still are shipping with a monitor of 1366*768 (ridiculous, right?). Let’s despise the nonsense the OEM told us and think about program running in computers with higher screen resolution.

RAM is not free, do not waste it

Obviously, costing 100+ mb of ram for showing a transparent window in 4K is unacceptable, especially compared with Win32 transparent window, which costs only 10+ mb. The gap between them makes WPF look dump.
Enough complaining, what can we do about it? I don’t want to write UI code with GDI using C++, that’s inefficient and not modern, plus, no one would abandon their beautiful, easy to maintain xaml UI code for this.

Hosting WPF content in Win32 Window

Well, indeed, no one would rewrite their UI code for just about 90mb of RAM. Compared with the work needed to rewrite C++ UI code, the RAM consumption gap seems acceptable (#smile face). But please remember, we can always host WPF content in win32 window.
Let say, we want to create a full screen notification dialog with semi-transparent background and apaque notication content in the center. To avoid the WPF ram problem, we create a semi-transparent window using win32:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD Flags1 = WS_EX_LAYERED;
DWORD Flags2 = WS_POPUP;

HWND hWnd = CreateWindowEx(Flags1,szWindowClass, szTitle, Flags2,
CW_USEDEFAULT, 0, 3840,2160, nullptr, nullptr, hInstance, nullptr);

SetLayeredWindowAttributes(hWnd, RRR, (BYTE)125, LWA_ALPHA);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

case WM_ERASEBKGND:
RECT rect;
GetClientRect(hWnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(0, 0, 0)));
break;

By enabling C++/CLI, we can access WPF content directly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);

UIElement^ page = gcnew ManagedContent::WPFContent();
source->RootVisual = page;
return (HWND)source->Handle.ToPointer();
}
}

and finally

1
2
//managed content
ManagedCode::GetHwnd(hWnd, 0, 0, 200, 200);

Due to the different technologies behind WPF and GDI, more work needed to be done for the unavoidable alpha blending problem, but, you can use wpf popup to achieve your goal for short.

“阻断疗法” - 拯救 WPF 启动过程中发生设备热插拔导致触摸失效问题

“阻断疗法” - 拯救 WPF 启动过程中发生设备热插拔导致触摸失效问题

如果你在WPF程序启动过程中进行设备热插拔(例如,插入一个U盘,一个USB摄像头),那么你的WPF程序很有可能失去所有触摸消息响应,通过 Tablet.TabletDevices.Count 检查当前程序的挂靠触摸设备,发现为0。有趣的是,如果你将触摸线重新插拔后,程序恢复正常。所以,这是WPF的Bug,微软的锅。那么这个锅的根本原因是啥?有兴趣的可以调试 .net framework 源码,这里没有深究。

阅读更多
WPF 渲染小结

WPF 渲染小结

在上一篇文章D3DImage - 它能做啥、解决了什么问题、有哪些瓶颈、怎么最佳实践最后,提到DropShadowEffect严重影响到D3DImage的渲染性能问题,导致程序在渲染8分屏(8个远端视频)的时候,出现严重的性能下降,渲染卡顿。要知道,在使用原生窗口渲染方案渲染8分屏,CPU占用和内存占用也不过25%和~200Mb,稍差一点,使用D3DImage优化后方案渲染,CPU占用并没有出现多大的跳跃,大约在30%左右。即使是添加了DropShadowEffect的情况下,CPU占用和内存占用好像都没有多大变化;既然在CPU和内存占用都没有多大变化的情况下,WPF渲染卡顿,那肯定(可能吧)是“帧生成”时间过长的锅。

阅读更多
D3DImage - 它能做啥、解决了什么问题、有哪些瓶颈、怎么最佳实践

D3DImage - 它能做啥、解决了什么问题、有哪些瓶颈、怎么最佳实践

D3dImage,.Net Framework 3.5 之后,微软提供的一个全新的ImageSource对象,可以在WPF中很好的呈现DirectX内容;在此之前,你只能将DirectX内容直接渲染在Windows窗口之上,这必然引起令人头疼的AirSpace问题,为了在这些内容上面添加我们习以为常的WPF UI 元素,你只能使用Popup来承载这些内容,完全丧失WPF UI开发的灵活性,且有经验的WPF程序员都知道一个事实:WPF Popup就是一个深坑 - 你需要手动处理各种显示隐藏问题、因为其导致的焦点问题,显示层级问题以及最令人头疼的性能问题,特别是在4K屏幕下,因为我们都知道,Popup就是一个Window,为了解决Airspace问题而使用Popup来承载UI必定需要使其AllowTransparency=True,这就引起了另外一个问题,透明窗口占用内存与其面积成正相关,在4K屏幕下,你可能将整个程序大部分的内存占用贡献给了这些Popup UI。说了这么多,好像在诉控Popup有多么的垃圾(它的确如此,如果在做大量的UI容器时)。

年轻人,如果你觉得Airspace问题真的没有办法解决了,只能用Popup这种技术手段来规避了,那么听老人一句话,不要浪费时间在Popup上了,因为你在前期投入的时间来规避种种Popup UI导致的问题以及各种你意想不到的Bug,到最后总会碰到解决不了,完全不能规避的情况,从而导致整个Popup UI替换方案完全失败的情形。

阅读更多
D3DImage in WPF

D3DImage in WPF

在.Net Framework 3.5 SP1中,微软在WPF中提供了D3DImage对象,D3DImage是一个ImageSource,这可以让我们在WPF原生的D3D Surface上渲染Direct3D Surface,大大提高了WPF和DirectX内容的交互性。
在此之前,要想在WPF上渲染DirectX的内容,只能让DirectX直接渲染到窗口上,这样会造成不可避免的Airspace问题,因为DirectX内容要时刻刷新重绘,导致WPF窗口上的其他内容被覆盖,表现就是DirectX内容始终在窗口最顶层。
正如前面所说,D3DImage是一个ImageSource,在WPF中,这意味着,我们可以将一个3D场景变成一个Image对象的Source,或者构建一个ImageBrush,这意味这D3D Surface可以渲染到WPF中任意一个以Brush进行渲染的元素上,例如图片,文本前景色,元素背景色等等。
如下,就是一个典型的应用D3DImage的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (d3dimg.IsFrontBufferAvailable)
{
IntPtr pSurface = IntPtr.Zero;
pSurface = _view.GetBackBuffer();

if (pSurface != IntPtr.Zero)
{
d3dimg.Lock();
d3dimg.SetBackBuffer(D3DResourceType.IDirect3DSurface9,pSurface);
_view.Draw();
d3dimg.AddDirtyRect(new Int32Rect(0, 0, d3dimg.PixelWidth, d3dimg.PixelHeight));
d3dimg.Unlock();
}
}

只需要Direct3D内容渲染的Surface接口指针即可,如上面的pSurface。

深挖 WPF 渲染系统

深挖 WPF 渲染系统

这是一篇2011年的文章,原地址在 A Critical Deep Dive into the WPF Rendering System,个人认为到现在都可以给WPF程序开发人员作为一个参考,里面详细讲述了 WPF 这个号称从底层支持硬件加速的 UI 框架为什么有时候看起来并不是那么回事的原因。以下是正文。


刚开始我并不认为我会发表这篇文章。在被一些我高度重视的人说服后,我决定要发表这篇文章。那些深入投入微软UX平台的程序员应该更加深入了解这个平台内部是怎么工作的,当他们撞到一睹石墙的时候,他们可以清晰了解到问题所在并且更加准确地沟通他们希望平台做出怎样的改变。

我相信 WPF 和 Silverlight 是精心打造的技术,但是…

如果这几个月你有关注我的Twitter,你也许会发现我一直在吐槽WPF(Silverlight也一样)的性能。为什么我会这样做?毕竟这些年我花费了大量的时间为这个平台布道、写库、社区帮助和指导等等。准确来讲,我个人全身投入到这个平台里面,我希望这个平台越来越好。

性能,性能,性能

当在开发沉浸式的、消费者导向的UX的时候,性能是你第一位的特性。性能是你添加其他特性的前提和基础。有多少次因为UI太卡你需要缩小UI的规模?有多少次因为这个技术做不到所以你需要丢弃“开创性的UX模型”?有多少次你告诉客户他们需要一个2.4GHz的四核CPU才能获得全部的体验?我一直以来都被客户问,为什么在PC上拥有4倍于iPad的性能的情况下,WPF程序却做不到像iPad应用那般流畅?

阅读更多