作者:Kenny Kerr 翻译:Ray Linn
在关于Direct2D技术的第三讲里,我将要展示其在互操作性上无与伦比的能力。我不打算遍历关于互操作性的所有细节,我想给你演示一个实际应用:分层窗口。分层窗口是那些已经久已存在且未被改进的Windows诸多特性之一,因此特别需要利用现代图形技术来提高它的使用效率。
这儿,我假定你有些Direct2D编程的基本知识。 如果没有,我建议你读下6月(我以前的文章:msdn.microsoft.com/magazine/dd861344 )和9月( msdn.microsoft.com/magazine/ee413543 )的MSDN有关文章,它们介绍了Direct2D的基础编程和绘图的有关内容。
一般来说,使用分层窗口总是为了些个不同的目标。通常,他们可以用来轻松,高效地制作的视觉效果和无闪烁的渲染。在过去GDI大行其道的日子里,使用分层窗口可算是个高招。但在普遍使用硬件加速的今天,分层窗口就有点落伍了,因为分层窗口仍然属于User32/GDI世界,且没有任何有效手段来支持高性能,高品质的微软图形平台-- DirectX。
分层窗口的确提供了某种独特的能力,它可以创建每像素不同透明度的窗口,目前Windows SDK尚未提供其他方式能实现同一目标。
应该指出的是,有两种分层窗口。这取决于是否需要控制每像素透明度或只需要控制整个窗口透明度。 本文的分层窗口是指前者,但如果你只需要控制整个窗口透明度,那么你可以在创建窗口之后,简单地调用之设置alpha值的SetLayeredWindowAttributes函数。
Verify(SetLayeredWindowAttributes(
windowHandle,
0, // no color key
180, // alpha value
LWA_ALPHA));
这儿假设你在采用了WS_EX_LAYERED扩展样式来创建窗口或在创建之后调用SetWindowLong来设置样式。这种方式的好处是显而易见的,你不需要对应用程序的重画窗口的方式做出任何改变,因为桌面窗口管理器(DWM)窗口会自动融合(Blend) 任何适当的窗口。如图:
在另一种情况下,你就需要自己处理一切。当然,如果使用了诸如Direct2D这样全新的渲染技术,这不是一个问题!
我们该如何进行? 从原理上来讲是相当直接的。首先,您需要填充一个UPDATELAYEREDWINDOWINFO结构,它提供了分层窗口的位置和大小,以及一个GDI设备上下文(DC),DC定义了窗口的表面,也潜藏了问题。DC是属于GDI这个旧世界的,且离硬件加速的DirectX的新世界甚远。
除了需要自己分配UPDATELAYEREDWINDOWINFO结构里的所有指针这些麻烦外,还有就是UPDATELAYEREDWINDOWINFO在Windows SDK中并未被详细说明,使用起来有些混乱。 笼统地讲,你需要为五个结构分配内存:通过DC复制的位图的所在位置,更新时窗口在桌面的位置,要复制的位图大小也就是窗口的大小:
POINT sourcePosition = {};
POINT windowPosition = {};
SIZE size = { 600, 400 };
然后是BLENDFUNCTION结构,它定义分层窗口将如何与桌面融合。这是一个令人惊讶的多功能结构,往往被忽视,但也可以非常有用。通常你可以如下填充它:
BLENDFUNCTION blend = {};
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
AC_SRC_ALPHA常量表明源位图有一个alpha通道,这是最常见的场景。
有意思的是,你可以象在SetLayeredWindowAttributes函数中一样使用SourceConstantAlpha来控制整个窗口的透明度。当它设为255时, 分层窗口将只使用每个像素的alpha值,你可以将其持续调整到零,即完全透明,来产生诸如渐入渐出的效果而不需要额外的重绘成本。这也是为什么它被称为BLENDFUNCTION结构的原因:这个结构的值生成了α-融合窗口。
最后我们如下填充LUPDATELAYEREDWINDOWINFO结构:
UPDATELAYEREDWINDOWINFO info = {};
info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO);
info.pptSrc = &sourcePosition;
info.pptDst = &windowPosition;
info.psize = &size;
info.pblend = &blend;
info.dwFlags = ULW_ALPHA;
上面的代码是相当清晰的,唯一未被提及的是dwFlags成员变量。如果你使用过UpdateLayeredWindow函数,那么ULW_ALPHA常量,看起来就相当熟悉,它表明应该使用融合功能。
最后,您需要提供源DC的句柄,并调用UpdateLayeredWindowIndirect函数来更新窗口:
info.hdcSrc = sourceDC;
Verify(UpdateLayeredWindowIndirect(
windowHandle, &info));
总的就是这样。该窗口将不会收到任何WM_PAINT消息。任何时候你需要显示或更新窗口,只需调用UpdateLayeredWindowIndirect功能。 我用一个LayeredWindowInfo包装类来未上面的代码做个模板以方便后续使用:
class LayeredWindowInfo {
const POINT m_sourcePosition;
POINT m_windowPosition;
CSize m_size;
BLENDFUNCTION m_blend;
UPDATELAYEREDWINDOWINFO m_info;
public:
LayeredWindowInfo(
__in UINT width,
__in UINT height) :
m_sourcePosition(),
m_windowPosition(),
m_size(width, height),
m_blend(),
m_info() {
m_info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO);
m_info.pptSrc = &m_sourcePosition;
m_info.pptDst = &m_windowPosition;
m_info.psize = &m_size;
m_info.pblend = &m_blend;
m_info.dwFlags = ULW_ALPHA;
m_blend.SourceConstantAlpha = 255;
m_blend.AlphaFormat = AC_SRC_ALPHA;
}
void Update(
__in HWND window,
__in HDC source) {
m_info.hdcSrc = source;
Verify(UpdateLayeredWindowIndirect(window, &m_info));
}
UINT GetWidth() const { return m_size.cx; }
UINT GetHeight() const { return m_size.cy; }
};
下面的代码演示了使用ATL/WTL的分层窗口和LayeredWindowInfo包装类的一个基本骨架.这首先要注意的是,代码没有必要调用UpdateWindow,因为此代码不使用WM_PAINT消息。 相反,它将立即调用Render方法,依次执行绘图,并提供DC给LayeredWindowInfo的Update方法。 绘图是如何发生、DC又从何而来,这是最有趣的地方。
class LayeredWindow :
public CWindowImpl<LayeredWindow,
CWindow, CWinTraits<WS_POPUP, WS_EX_LAYERED>> {
LayeredWindowInfo m_info;
public:
BEGIN_MSG_MAP(LayeredWindow)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
LayeredWindow() :
m_info(600, 400) {
Verify(0 != __super::Create(0)); // parent
ShowWindow(SW_SHOW);
Render();
}
void Render() {
// Do some drawing here
m_info.Update(m_hWnd,
/* source DC goes here */);
}
void OnDestroy() {
PostQuitMessage(1);
}
};
GDI/GDI+的方法
我会先告诉您是在GDI/GDI+中是如何进行的。首先你需要创建一个乘以32字节/像素的位图,它采用蓝-绿-红-alpha(BGRA)颜色通道字节顺序。预先乘以32意味着颜色通道的值已经和alpha值相乘。这会给alpha融合图形带来更好的性能,但它也意味着你需要将颜色值除以alpha值以得到真正的颜色值。在GDI的术语中,这称为32BPP的设备无关位图(DIB),它通过填充BITMAPINFO结构,并传递给CreateDIBSection函数来创建。
BITMAPINFO bitmapInfo = {};
bitmapInfo.bmiHeader.biSize =
sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth =
m_info.GetWidth();
bitmapInfo.bmiHeader.biHeight =
0 – m_info.GetHeight();
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression =
BI_RGB;
void* bits = 0;
CBitmap bitmap(CreateDIBSection(
0, // no DC palette
&bitmapInfo,
DIB_RGB_COLORS,
&bits,
0, // no file mapping object
0)); // no file offset
这儿有很多细节我们尚未涉及。这个API函数还有很长的路要走。应该注意的是,我指定为位图的负高度。BITMAPINFOHEADER结构定义位图是自上而下还是自下而上。如果高度为正,你得到一个自下而上的位图,反之,如果高度为负,则得到自下而上的位图。自上而下的位图,它们的原点在左上角,而自下而上的位图,原点则在左下角。
虽然不是严格要求,但我还是倾向使用自下而上的位图,因为这是现在的Windows图形处理元件的最常见格式,这样可以提高互操作性。我们可以通过以下方法计算出一个为正值的stride:
UINT stride = (width * 32 + 31) / 32 * 4;
现在,您有足够的信息来通过位指针绘制位图。除非您完全疯了,要使用绘图函数来实现,不幸地是,GDI提供的大部分函数并不支持alpah通道。这是GDI+的用武之地。
虽然你可以把位图的数据直接传递给GDI+,不过我们只要为它创建一个DC,反正我们传递给UpdateLayeredWindowIndirect函数的就只是它.要创建DC,我们需要调用恰当地命名为CreateCompatibleDC的函数,它会创建一个与桌面兼容的内存DC。然后,可以调用SelectObject函数来将位图选入DC中。下面的GdiBitmap包装类包含了这些有的没的功能:
class GdiBitmap {
const UINT m_width;
const UINT m_height;
const UINT m_stride;
void* m_bits;
HBITMAP m_oldBitmap;
CDC m_dc;
CBitmap m_bitmap;
public:
GdiBitmap(__in UINT width,
__in UINT height) :
m_width(width),
m_height(height),
m_stride((width * 32 + 31) / 32 * 4),
m_bits(0),
m_oldBitmap(0) {
BITMAPINFO bitmapInfo = { };
bitmapInfo.bmiHeader.biSize =
sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth =
width;
bitmapInfo.bmiHeader.biHeight =
0 - height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression =
BI_RGB;
m_bitmap.Attach(CreateDIBSection(
0, // device context
&bitmapInfo,
DIB_RGB_COLORS,
&m_bits,
0, // file mapping object
0)); // file offset
if (0 == m_bits) {
throw bad_alloc();
}
if (0 == m_dc.CreateCompatibleDC()) {
throw bad_alloc();
}
m_oldBitmap = m_dc.SelectBitmap(m_bitmap);
}
~GdiBitmap() {
m_dc.SelectBitmap(m_oldBitmap);
}
UINT GetWidth() const {
return m_width;
}
UINT GetHeight() const {
return m_height;
}
UINT GetStride() const {
return m_stride;
}
void* GetBits() const {
return m_bits;
}
HDC GetDC() const {
return m_dc;
}
};
GDI+的图形类,提供了在设备上绘图的一些方法,可以用来构造位图的DC。下面代码显示了如何让上面的LayeredWindow类用GDI+进行渲染。一旦我们把它和代码样板相结合,就相当直接:窗口的大小被传递给GdiBitmap的构造函数,位图的DC被传给Graphics的构造函数和Update方法。虽然简单,但GDI或GDI+的大部分函数都不支持硬件加速,也没有提供特别强大的渲染功能。
class LayeredWindow :
public CWindowImpl< ... {
LayeredWindowInfo m_info;
GdiBitmap m_bitmap;
Graphics m_graphics;
public:
LayeredWindow() :
m_info(600, 400),
m_bitmap(m_info.GetWidth(), m_info.GetHeight()),
m_graphics(m_bitmap.GetDC()) {
...
}
void Render() {
// Do some drawing with m_graphics object
m_info.Update(m_hWnd,
m_bitmap.GetDC());
}
...
架构问题
相反的,在WPF里创建一个分层窗口只需要以下几步:
class LayeredWindow : Window {
public LayeredWindow() {
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
// Do some drawing here
}
}
虽然非常简单,但它掩盖了其中的复杂性和使用分层窗口的架构限制。不管你如何优化它,分层窗口仍然必须遵循的在这篇文章里阐述的架构原则。虽然WPF中可以用硬件加速来进行渲染,但得到的结果仍然需要被复制到预乘的BGRA位图中,在调用UpdateLayeredWindowIndirect更新显示之前,它仍需被选入一个兼容的DC。WPF暴露出来的东西并不比一个bool变量多,它不得不为你无法控制的行为做些适当的选择。但为什么这是个问题?因为这涉及到硬件。
图形处理单元(GPU)提供了专用内存以达到最佳的性能。这意味着,如果你需要处理现有的位图,它就会从系统内存(RAM)复制到GPU内存,这往往要比在系统内存的两点间复制要慢得多。 反过来也是如此:如果你使用GPU创建和渲染位图,然后将它复制到系统内存,这也是一个昂贵的复制操作。
通常,这种情况不该发生的,GPU所渲染的位图应该直接被送往显示设备。但在分层窗中中,位图则必须被传送回系统内存,因为User32/GDI调用了需要访问位图的内核态和用户态的资源。例如User32里鼠标点击分层窗口的情况,在分层窗口上的点击与位图的alpha值是相关的,如果点击的地方是透明的,那么鼠标的消息会穿透该分层窗口。因此,系统内存里需要一个位图的副本来应付这种情况。一旦用UpdateLayeredWindowIndirect来拷贝位图,它又被直接送回GPU让DWM能够组合出桌面。
除了往返内存复制的开销,GPU和CPU之间的同步也是昂贵的。不象典型的CPU操作,GPU的操作往往都是异步的,当批量执行一系列渲染指令时,这提供了很好的性能。每次我们需要跨越到CPU时,GPU不的不强制清空批处理命令,而CPU不得不等待GPU完成操作,而达不到最佳性能。
这意味着我们需要对这些往返操作的开销和频率提高警惕。如果正在呈现的场景足够复杂,那么硬件加速性能可以轻松超过复制位图的开销。反之,如果渲染的开销不大,可以由CPU来进行,你可能会发现,没有硬件加速反而提供了更好的性能能。做出选择并不容易。有些图形处理器甚至没有专用的内存,而使用的系统内存,从而降低了部分复制开销。
但是不管是WPI还是GDI都不会给你选择的机会。在GDI的情况下,你只能是用CPU,而在
WPF里,你总被迫是用WPF的渲染方式,通常是硬件加速的Direct3D。
这时Direct2D来了。
- 大小: 35.2 KB
分享到:
相关推荐
利用分层窗口(layered window)+ PNG图片实现,GDI+绘制,原理很简单。 1、双击面板编辑备忘 2、双击中间黄点,设置
利用分层窗口(layered window)+ PNG图片实现,原理很简单。 1、双击面板编辑备忘 2、双击中间黄点,设置
比较新的对比度增强论文,论文《CONTRAST ENHANCEMENT BASED ON LAYERED DIFFERENCE REPRESENTATION》是一种思路比较新颖的利用直方图思想来进行对比度增强的算法,经常在低光增强的算法比较中见到。其核心思想是...
利用分层窗口(layered window)+ PNG图片实现,GDI+绘制,原理很简单。 1、双击面板编辑备忘 2、双击中间黄点,设置
分层窗口的实现,VC开发
最基础的分层窗口代码,显示一张图片,涉及的主要API为:UpdateLayeredWindow。创建窗口时设置窗口类型WS_EX_LAYERED。
利用创建分层窗体的方法实现窗体的透明显示,可以作为老板软件使用。目前为0.1.0版本,添加了窗口名称显示功能,窗口大小设置的功能。
layered窗体通过GDI+绘制带有alpha通道的图片实现窗体阴影,该案例基于MFC和ATL实现
vb6实现的layeredwindow窗口技术,重新绘制漂亮的窗口,包含两个实例,实现类似Windows侧边栏小工具的效果。
1、有没有方法创建一个半透明的窗口,并将该窗口上发生的所有鼠标事件都传递到桌面或另一个应用窗口处理?2、我正在写一个幻灯显示程序,该程序要显示JPEG图像序列。我使用了 2002年三月刊...关键字:layered windows,b
介绍编写半透明窗口的源代码,API的NO—MFC,JUST DO IT!
Acoustic wave propagation in layered media is very important topic for many practical applications including medicine, optics and applied geophysics. The key parameter controlling all effects in ...
This paper proposes a method called implicit encapsulation for creating layered stimulus models for use in constrained random verification environments. The proposed method relies heavily on the VMM ...
支持标准Winform控件的透明窗体,支持半透明PNG背景图,非双窗体技术的另一种实现方案。 含源代码
Ajax-magento2-ajax-layered-navigation.zip,ajax分层导航magento 2提供了一个过滤器列表,帮助您的客户以最短的方式搜索和获得他们最喜欢的产品。这个扩展应用了现代ajax技术来增强过滤系统,以提高用户对页面上每...
最近改玩WinRT和Metro了,看到有些朋友还在玩基于GDI的DirectUI,正好自己以前也尝试写过的,反正放着也没用,开源吧。 这个DirectUI界面库主要参考一个老外的程序, 支持各种Layout模式,实现了各种基本控件(Split...
layered 旨在实现一个干净的前馈神经网络。它用 Python 3写成,使用 MIT 许可证。如果你想实现神经网络,你可以使用这个库作为指导。 标签:layered
layered_hardware 采用分层方案的ros_control实现分层方案每个ros_control的组件(例如joint_limits,transmissions)都实现为图层插件(base_class: ) 一个人可以针对不同的执行器重用非执行器特定层的插件节点:...
3、创建分层透明窗口 4、绘画分层窗口填充背景 ' 实现目标(1): ' DEMO_No1_CRETATE_default () ' 实现目标(2): ' DEMO_No1_CRETATE_noborder () ' 实现目标(3): ' DEMO_No1_CRETATE_layered ()
1.将窗口扩展属性设置为分层属性WS_EX_LAYERED。 2.选一张透明的png图片,并将其加载进来。 3.创建与窗口兼容的内存设备上下文,以及兼容位图,将兼容位图选入兼容设备上下文。 4.将png图片绘制到内存设备上下文中。...