DFB 与 DIB






4.91/5 (107投票s)
2004 年 6 月 17 日
10分钟阅读

231362
DFB 与 DIB 之间的区别。
引言
什么是位图(bitmap),什么是 DIB?它们之间有区别吗?为什么 Windows API 中有 CreateBitmap
、CreateCompatibleBitmap
、CreateDIBitmap
和 CreateDIBSection
等函数?SetBitmapBits
和 SetDIBits
又有什么区别?
老实说,很长一段时间以来,我对这些问题都没有答案。我猜我不是唯一一个。所有这些对我来说都变得完全清楚,直到我必须编写自己的内核模式视频驱动程序。
我相信每个使用 GDI 的程序员都应该知道这些答案。
2D 加速史诗
什么是显卡?显然,它是一种硬件设备,能够存储图像并将其传输到某些显示设备(CRT 显示器等)。图像存储在显卡的内部内存(显存)中,显卡以一定的速率(刷新率)为 CRT 阴极射线生成信号。这只是笼统的说法;今天的显卡功能要强大得多。
图像的大小通常是多少?它们占用多少资源?可以说,一个 640x480、8 位/像素的图像大约占用 300k,一个 800x600、16 位/像素的图像占用 937.5k,如果是一个 1024x768、24 位/像素的图像,我们将达到 2.25Mb。这很多!如果你想显示这样的图像,你必须通过总线传输超过 2Mb 的数据。现在假设你想以某种方式动画这个图像(例如,移动它)。这将占用大量的系统资源。如果你想对屏幕上的图片进行一些调整:你必须将其从显存中拉出,修改它,然后将其上传回去。此外,考虑绘制的复杂性:绘制这样的图像涉及修改数十万像素,这对 CPU 来说是一个巨大的负担。
所有这些导致了 2D 成像中的几项发明。
- 处理绘图指令的显卡。这意味着,为了在屏幕上绘制“hello world”,你不再需要合成输出图像然后将其上传到显存。现在,你只需将指令及其所有参数(字体、字符串、画笔、位置等)发送到显卡内部处理器,聪明的显卡就会完成所有工作。你节省了 CPU 时钟(在显卡处理器工作期间,CPU 可以做其他工作),而且你几乎不需要通过总线传输任何数据,只需传输绘图参数即可。
- 设备管理位图(和帧缓冲)。假设你想在图像上方显示一些内容,然后将其移除。当然,屏幕是平坦的,没有“上方”这种东西。理论上,你必须下载图像中被遮挡的部分并将其存储在某个地方(位图),在那里显示另一个图像,然后将保存的位图上传回去。如今,不再需要这样做。显卡拥有比一个屏幕占用多得多的显存。这部分显存用于所谓的设备管理位图。显卡可以在它们之间,以及它们与屏幕之间执行绘图操作。同样,这带来了巨大的性能优势。
术语
在继续之前,我们先统一一下术语。
- 位图(Bitmap)。它是一种表示矩形图像的数据块。它具有大小和特定的格式。从历史上看,位图("bit-map")表示具有指定颜色深度(每像素位数)的矩阵,其中每个位以某种方式表示特定像素的颜色(或其一部分)。但在 Windows 98/2K 下,位图也可以有压缩格式:JPG、PNG、RLE,因此严格来说,位图并非总是简单的矩阵。
- 设备上下文 (DC)。它只是一个抽象结构,包含几个属性和指向其他绘图对象的句柄。它不是一个设备,它不消耗大量资源。你不会在 DC 上绘图。实际上,你在位图(已选择到 DC 中)上绘图,或者在由该 DC 表示的设备上绘图。实际上,它只需要为绘图函数提供一些默认参数;否则每个这样的函数都会接受数十个参数。例如,当你调用
TextOut
时,你只提供字符串及其位置,而还有字体、背景和前景颜色、填充模式、剪裁区域、坐标变换等。 - 设备无关位图 (DIB)。它是一种具有已知像素格式的位图。它包含的数据(位)驻留在系统内存中,可以由应用程序直接访问。换句话说,要在此位图上绘制“hello world”,你可以将其选择到 DC 中并调用
TextOut
函数(或其他),或者你可以直接修改其位(你必须知道是哪些位)。效果将是相同的。 - 设备格式位图(DFB),或设备相关位图(DDB)。它是一种由硬件设备(通常是显卡)管理的位图。制造商可以自由地以他想要的方式管理图像,因此像素格式未定义。因此,你无法直接访问图像数据。它通常也保存在非系统内存(显存)中。要在其上绘制“hello world”,你可以使用 GDI 的
TextOut
函数,该函数最终会调用显卡驱动程序。
我特意更精确地定义了术语。这是因为 GDI API 中的函数名称非常容易混淆。从 GDI 函数名称来看,可能会得出“位图”与 DIB 相反的结论,而实际上,DIB 只是“位图”的一种特殊情况。实际上,DIB 与 DFB 相反,两者都是位图,但管理方式不同。
本应有 CreateDIBxxx
和 CreateDFBxxx
函数,而 GDI 将它们称为 CreateDIBxxx
和 CreateBitmapxxx
。在某些地方(但不是所有地方),“Bitmap”一词实际上应该替换为 DFB。所以——永远不要依赖函数名!总是查看它的描述。
关于 Windows 视频驱动程序架构的几句话。
Windows 视频驱动程序架构旨在尽可能多地利用显卡的硬件 2D 加速。但是,显卡不必支持所有已知的 2D 加速技术;事实上,它根本不必支持任何一种。
发送给显卡的每个绘图指令最终都会在显卡驱动程序的某个调用点结束。该驱动程序负责以适当的方式(这取决于制造商)操作显卡,以便视频驱动程序扮演硬件抽象层的角色;它为硬件提供了一个与硬件无关的接口。
视频驱动程序必须实现一组非常基本的功能。它包括为设备设置特定的视频模式,以及向设备传输位和从设备传输位。因此,视频卡根本不需要支持 2D 加速。
此外,视频驱动程序还可以实现一组功能。它包括管理自己的位图(DFB),并支持在其上(以及它们之间)进行多种绘图操作,例如位块传输(带或不带像素格式转换)、文本函数、Alpha 混合、拉伸、渐变填充等。每个这样的函数,即使驱动程序已向操作系统报告,也有权随时拒绝工作。
那是什么意思?首先,您必须明白视频卡驱动程序仅参与对其管理的表面(即显示器或 DFB)进行绘图操作。如果您在 DIB 上绘制内容,则视频卡根本不参与。
现在,假设您调用 AlphaBlend
GDI 函数,而 DC 表示显示器或 DFB。首先,它检查显卡是否支持 Alpha 混合加速(如果支持,它必须提供指向其函数的指针)。如果支持,操作系统会调用驱动程序提供的 DrvAlphaBlend
函数,否则会调用 Windows 的 EngAlphaBlend
函数。显卡驱动程序会检查在其 DrvAlphaBlend
函数中提供的参数,并决定是否要处理它。如果它处理,它会以硬件相关的方式调用显卡(通过所谓的微型端口驱动程序);否则,驱动程序可以自由地调用 EngAlphaBlend
函数来完成工作,就好像它根本没有提供 DrvAlphaBlend
一样。
那么,EngAlphaBlend
函数做了什么?它必须以某种方式执行 Alpha 混合操作,尽管视频驱动程序不支持它。在这种情况下,操作系统会将请求的操作分解为更简单的任务,并再次调用视频驱动程序。例如,它可以在系统内存中合成结果图像(DIB),然后要求视频驱动程序绘制该 DIB。或者它可以将源参数(也可以是位图)转换为其他格式,从而消除驱动程序在不同格式之间进行转换的需要,然后重试 DrvAlphaBlend
。同样,驱动程序有权不处理该调用,并且操作系统必须继续简化驱动程序的工作。通过这种方式,它可以达到一个非常基本的级别,驱动程序无法拒绝执行。
因此,正如我们所看到的,Windows 驱动程序架构知道如何利用硬件 2D 加速,而显卡唯一必须做的事情是知道如何将图像传输到显示器并从显示器传输图像。
DFB vs DIB。
了解 DFB 和 DIB 的区别后,我们来讨论它们的优势。
在 DFB 上进行绘图操作是硬件加速的(至少部分是),因此它们通常非常快。同样,它们之间以及它们与显示器之间的传输也非常快。那么,我们为什么要使用 DIB 呢?
原因是有时候我们需要的绘图输出非常复杂,无法用标准 GDI 函数来表达。在这种情况下,最好有一个指向图像位的系统内存指针并直接修改它们。当然,这不是一种简单的方法,但它提供了最佳的性能,并且通常被图形导向的程序使用,例如 Photoshop、ACDSee 等。另一种方法是只使用 SetPixel
和 GetPixel
函数,逐像素构建图像。对 DFB 这样做会导致灾难性的性能下降,因为每次这样的调用都涉及繁重的事务,而对 DIB 调用这些函数是可以的。
GDI API。
我们如何创建 DFB 和 DIB?首先:没有一个函数能保证创建 DFB!正如我们所知,视频驱动程序根本不需要支持 DFB。即使支持,它也可能拒绝,要么是因为显存不足,要么是因为您请求的像素格式不支持或未被显卡加速。有些函数会尝试创建 DFB。如果失败,它们会创建一个具有适当参数的 DIB 并返回。这些函数包括 CreateBitmap
、CreateBitmapIndirect
、CreateCompatibleBitmap
(当 DC 引用 DFB 或设备时)和 CreateDIBitmap
,尽管它的名称令人困惑。
顺便说一下,我查看了一些视频驱动程序的源代码。它们都只支持与当前显示器格式相同的像素格式的 DFB,其中一些无论当前视频模式如何都支持单色 DFB。
然而,有一个函数保证能创建一个 DIB。而且,只有一个这样的函数。它叫做 CreateDIBSection
。(再次强调,永远不要依赖函数名,务必查看其描述!)
现在,假设我们有一个位图句柄,HBITMAP
。我们怎么知道它指的是 DFB 还是 DIB 呢?调用 GetObjectType
没有帮助,因为它们都是 OBJ_BITMAP
。幸运的是,有一种方法可以区分它们。我偶然发现了它。你可以调用 GetObject
函数来用位图的参数填充 BITMAP
结构。如果 bmBits
成员为 NULL
,则位图是 DFB,否则它是 DIB。
有用的小贴士。
假设您有一个位图用于一些自绘 GUI 元素或动画。而且这个位图不会改变;您不打算在其上绘图。事实上,DBF 在这里会比 DIB 更高效。所以,如果您自己创建它,请使用 CreateBitmap
尝试创建一个 DFB。但有时您会从其他函数(例如 LoadBitmap
、LoadImage
等)接收到位图句柄。其中一些通常会尝试创建 DFB,而另一些则不会。在这种情况下,以下两个转换函数会很有用。
// This function converts the given bitmap to a DFB. // Returns true if the conversion took place, // false if the conversion either unneeded or unavailable bool ConvertToDFB(HBITMAP& hBitmap) { bool bConverted = false; BITMAP stBitmap; if (GetObject(hBitmap, sizeof(stBitmap), &stBitmap) && stBitmap.bmBits) { // that is a DIB. Now we attempt to create // a DFB with the same sizes, and with the pixel // format of the display (to omit conversions // every time we draw it). HDC hScreen = GetDC(NULL); if (hScreen) { HBITMAP hDfb = CreateCompatibleBitmap(hScreen, stBitmap.bmWidth, stBitmap.bmHeight); if (hDfb) { // now let's ensure what we've created is a DIB. if (GetObject(hDfb, sizeof(stBitmap), &stBitmap) && !stBitmap.bmBits) { // ok, we're lucky. Now we have // to transfer the image to the DFB. HDC hMemSrc = CreateCompatibleDC(NULL); if (hMemSrc) { HGDIOBJ hOldSrc = SelectObject(hMemSrc, hBitmap); if (hOldSrc) { HDC hMemDst = CreateCompatibleDC(NULL); if (hMemDst) { HGDIOBJ hOldDst = SelectObject(hMemDst, hDfb); if (hOldDst) { // transfer the image using BitBlt // function. It will probably end in the // call to driver's DrvCopyBits function. if (BitBlt(hMemDst, 0, 0, stBitmap.bmWidth, stBitmap.bmHeight, hMemSrc, 0, 0, SRCCOPY)) bConverted = true; // success VERIFY(SelectObject(hMemDst, hOldDst)); } VERIFY(DeleteDC(hMemDst)); } VERIFY(SelectObject(hMemSrc, hOldSrc)); } VERIFY(DeleteDC(hMemSrc)); } } if (bConverted) { VERIFY(DeleteObject(hBitmap)); // it's no longer needed hBitmap = hDfb; } else VERIFY(DeleteObject(hDfb)); } ReleaseDC(NULL, hScreen); } } return bConverted; } // This function converts the given bitmap to a DIB. // Returns true if the conversion took place, // false if the conversion either unneeded or unavailable bool ConvertToDIB(HBITMAP& hBitmap) { bool bConverted = false; BITMAP stBitmap; if (GetObject(hBitmap, sizeof(stBitmap), &stBitmap) && !stBitmap.bmBits) { // that is a DFB. Now we attempt to create // a DIB with the same sizes and pixel format. HDC hScreen = GetDC(NULL); if (hScreen) { union { BITMAPINFO stBitmapInfo; BYTE pReserveSpace[sizeof(BITMAPINFO) + 0xFF * sizeof(RGBQUAD)]; }; ZeroMemory(pReserveSpace, sizeof(pReserveSpace)); stBitmapInfo.bmiHeader.biSize = sizeof(stBitmapInfo.bmiHeader); stBitmapInfo.bmiHeader.biWidth = stBitmap.bmWidth; stBitmapInfo.bmiHeader.biHeight = stBitmap.bmHeight; stBitmapInfo.bmiHeader.biPlanes = 1; stBitmapInfo.bmiHeader.biBitCount = stBitmap.bmBitsPixel; stBitmapInfo.bmiHeader.biCompression = BI_RGB; if (stBitmap.bmBitsPixel <= 8) { stBitmapInfo.bmiHeader.biClrUsed = 1 << stBitmap.bmBitsPixel; // This image is paletted-managed. // Hence we have to synthesize its palette. } stBitmapInfo.bmiHeader.biClrImportant = stBitmapInfo.bmiHeader.biClrUsed; PVOID pBits; HBITMAP hDib = CreateDIBSection(hScreen, &stBitmapInfo, DIB_RGB_COLORS, &pBits, NULL, 0); if (hDib) { // ok, we're lucky. Now we have // to transfer the image to the DFB. HDC hMemSrc = CreateCompatibleDC(NULL); if (hMemSrc) { HGDIOBJ hOldSrc = SelectObject(hMemSrc, hBitmap); if (hOldSrc) { HDC hMemDst = CreateCompatibleDC(NULL); if (hMemDst) { HGDIOBJ hOldDst = SelectObject(hMemDst, hDib); if (hOldDst) { if (stBitmap.bmBitsPixel <= 8) { // take the DFB's palette and set it to our DIB HPALETTE hPalette = (HPALETTE) GetCurrentObject(hMemSrc, OBJ_PAL); if (hPalette) { PALETTEENTRY pPaletteEntries[0x100]; UINT nEntries = GetPaletteEntries(hPalette, 0, stBitmapInfo.bmiHeader.biClrUsed, pPaletteEntries); if (nEntries) { ASSERT(nEntries <= 0x100); for (UINT nIndex = 0; nIndex < nEntries; nIndex++) pPaletteEntries[nEntries].peFlags = 0; VERIFY(SetDIBColorTable(hMemDst, 0, nEntries, (RGBQUAD*) pPaletteEntries) == nEntries); } } } // transfer the image using BitBlt function. // It will probably end in the // call to driver's DrvCopyBits function. if (BitBlt(hMemDst, 0, 0, stBitmap.bmWidth, stBitmap.bmHeight, hMemSrc, 0, 0, SRCCOPY)) bConverted = true; // success VERIFY(SelectObject(hMemDst, hOldDst)); } VERIFY(DeleteDC(hMemDst)); } VERIFY(SelectObject(hMemSrc, hOldSrc)); } VERIFY(DeleteDC(hMemSrc)); } if (bConverted) { VERIFY(DeleteObject(hBitmap)); // it's no longer needed hBitmap = hDib; } else VERIFY(DeleteObject(hDib)); } ReleaseDC(NULL, hScreen); } } return bConverted; }