DirectDraw 扩展





3.00/5 (2投票s)
1999年11月28日
6分钟阅读

167589

2995
一个由模板类组成的 DirectDraw 框架。
本文提供以下内容
- 一个由模板类组成的 DirectDraw 框架。
- 一个通用的blit例程,涵盖所有可用的像素格式
- 作为礼物,使用此框架在DirectShow下实现的,全速全屏视频播放器
1. 通过模板类实现的DirectDraw框架
众所周知,所有DirectDraw对象和表面都通过标准的COM方法调用进行处理,即QI、AddRef(隐藏)、Release。AddRef()调用大多是隐藏的,因为任何时候你查询一个接口,COM组件都会自动为你调用AddRef()。因此,虽然也要牢记,当指针被复制时,开发者应该使用AddRef(),但不得不说,管理Release()相当乏味。ATL框架提供了一个解决方案,即CComPtr和CComQIPtr模板类。这些类为我们隐藏了Release()调用,使我们能够专注于重要的事情,而不是AddRef()/Release()计数问题。非常棒。
由于在本篇文章中很重要,以下是ATL库(© Microsoft)中的CComPtr类的源代码(#include "atlbase.h")
template <class T> class CComPtr { public: typedef T _PtrClass; CComPtr() {p=NULL;} CComPtr(T* lp) { if ((p = lp) != NULL) p->AddRef(); } CComPtr(const CComPtr<T>& lp) { if ((p = lp.p) != NULL) p->AddRef(); } ~CComPtr() {if (p) p->Release();} void Release() {if (p) p->Release(); p=NULL;} operator T*() {return (T*)p;} T& operator*() {_ASSERTE(p!=NULL); return *p; } //The assert on operator& usually indicates a bug. If this is really //what is needed, however, take the address of the p member explicitly. T** operator&() { _ASSERTE(p==NULL); return &p; } T* operator->() { _ASSERTE(p!=NULL); return p; } T* operator=(T* lp){return (T*)AtlComPtrAssign((IUnknown**)&p, lp);} T* operator=(const CComPtr<T>& lp) { return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p); } #if _MSC_VER>1020 bool operator!(){return (p == NULL);} #else BOOL operator!(){return (p == NULL) ? TRUE : FALSE;} #endif // pointer member T* p; };
文章的标题是DirectDraw扩展,那为什么我还要谈论ATL呢?事实上,我想到的主要事情是我想克服DirectDraw固有的BLIT限制。请看下一节。为无法替换的接口添加功能的直接方法是,将所有功能包装在另一个类中。在这个类中,对Blit()函数的调用将委托给标准的IDirectDrawSurface->Blt()功能,或者通过我们自己的blit处理。
为什么使用模板?虽然文章只关注IDirectDrawSurface接口,因为Blit()是通过它实现的(DirectDraw不支持聚合),但事实上,包装类的概念可以推广到所有DirectDraw接口:IDirectDraw、IDirectDrawSurface、IDirectDrawClipper、IDirectDrawPalette,更不用说IDirectDraw2、IDirectDraw3、IDirectDraw4等等……事实上,这样一套模板类就是我所说的框架,并且可以完全替代用C++编写的标准DirectDraw应用程序。更不用说这个对象也适用于任何COM接口。
重点不仅仅在于使用包装模板类来操作或替代标准的DirectDraw COM操作。重点在于提供一个框架,该框架的设计思路是让这些类无缝集成到“老式”DirectDraw应用程序中。这才是有趣的地方。
现在我将给出根模板类和CDDSurface类的源代码。
#include "atlbase.h" template <class T> class CDDObject { public : // --- member CComPtr<T> m_pObject; // --- methods CDDObject() { } ~CDDObject() { Release(); } // operator & T **operator&() { return &m_pObject; } // operator T* : cast from CComPtr::T* to T* operator T*() { return m_pObject; } // operator ! : check whether the object is initialized BOOL operator!() { return ((T*)m_pObject==NULL) ? TRUE : FALSE; } // force destroy HRESULT Release() { m_pObject.Release(); return S_OK; } };
我实现的几个运算符实际上提供了对隐藏指针的便捷访问,并促进了与标准DirectDraw应用程序的无缝集成。例如,在查询接口(&运算符)时,或者想要向下转换时,或者想要检查指针(!运算符)时,都会使用这些运算符。在本文的最后,我提供了一个使用该类的完整源代码。
#ifndef _CDDSURFACE #define _CDDSURFACE #include <ddraw.h> #include "CDDObject.h" class CDDSurface : public CDDObject{ public : CDDSurface(); ~CDDSurface(); // the main method HRESULT Blt(RECT *destRect, IDirectDrawSurface *pSourceSurface, RECT *sourceRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx); }; #endif
正如您所看到的,CDDSurface继承了类型为T=IDirectDrawSurface的“核心”CDDObject模板类。该类提供了我们blit例程的实现。
请注意,只有Blit被委托,所有其他标准的IDirectDrawSurface功能通过显式转换进行访问,例如
CDDSurface my_surface; ... HDC my_dc; ((IDirectDrawSurface*)my_surface)->GetDC(&my_dc);
2. 一个通用的blit例程
在本节中,我们将实现blit例程。
那么有什么问题呢?让我们首先说,GDI通过两个WIN32方法执行blit,即::BitBlit()和::StretchBlit()。StretchBlit方法是最通用的方法,因为它可以同时进行缩放和blit。但它很慢。当你知道你只想执行一个简单的复制blit时,BitBlit会更快。此外,无论你的缓冲区是位图还是DIB Section,还有其他几种方法。
在DirectDraw的上下文中,有一个单一的BLT方法,它的作用类似于::StretchBlit()方法。DirectDraw比GDI强大得多,因为它能够在HAL/HEL抽象中利用你的硬件。
但是,由于存在一个真正的缺点,DirectDraw不知道如何在不同像素格式的DirectDraw表面之间进行blit。例如,在16位表面和24位表面之间。很多时候,你加载一个256色位图,然后希望将其转换为一个真正的DirectDraw表面以blit到主表面。由于主表面具有桌面深度,即通常为24或32位,所以这并不容易!
现在来解决这个问题。GDI不仅进行blit,还根据需要执行颜色映射。因此,这个想法,正如你所见,是相当简单的:当表面颜色深度相同时,将blit委托给DirectDraw HAL/HEL blit引擎;当表面颜色深度不同时,将blit委托给简单的GDI ::StretchBlit。
废话不多说,现在来看代码
#include "CDDSurface.h" // Blt extension method : performs a color stretch blit // // The blit operation copies the content of a direct draw surface // to another direct draw surface, without care of the size and color format // of the source and destination surfaces. Hence this blit encompasses // both approaches. HRESULT CDDSurface::Blt(RECT *destRect, IDirectDrawSurface *pSourceSurface, RECT *sourceRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { // blit void surfaces : just kidding or.... if (pSourceSurface==NULL || !m_pObject || !destRect || !sourceRect) return E_FAIL; if (destRect->bottom-destRect->top==0 || destRect->right-destRect->left==0 || sourceRect->bottom-sourceRect->top==0 || sourceRect->right-sourceRect->left==0) return S_OK; IDirectDrawSurface *pDestSurface=(IDirectDrawSurface*)m_pObject; DDSURFACEDESC ddsdDest,ddsdSource; ::ZeroMemory(&ddsdDest, sizeof(ddsdDest)); ddsdDest.dwSize = sizeof(ddsdDest); ::ZeroMemory(&ddsdSource, sizeof(ddsdSource)); ddsdSource.dwSize = sizeof(ddsdSource); HRESULT hr = pDestSurface->GetSurfaceDesc(&ddsdDest); if (SUCCEEDED(hr)) { hr = pSourceSurface->GetSurfaceDesc(&ddsdSource); if (SUCCEEDED(hr)) { // check the two pixel formats (surface type & bit count) BOOL bSamePixelFormat= (ddsdDest.ddpfPixelFormat.dwFlags==ddsdSource.ddpfPixelFormat.dwFlags) && (ddsdDest.ddpfPixelFormat.dwRGBBitCount==ddsdSource.ddpfPixelFormat.dwRGBBitCount); if (!bSamePixelFormat) { // perform the color blit using the GDI (implicitely will remap colors) HDC dcDest,dcSource; pSourceSurface->GetDC(&dcSource); pDestSurface->GetDC(&dcDest); int nOldStretchMode = ::SetStretchBltMode( dcDest, COLORONCOLOR); ::StretchBlt(dcDest, 0,0, destRect->right-destRect->left, destRect->bottom-destRect->top, dcSource, 0,0, sourceRect->right-sourceRect->left, sourceRect->bottom-sourceRect->top, SRCCOPY); ::SetStretchBltMode( dcDest, nOldStretchMode); pDestSurface->ReleaseDC(dcDest); pSourceSurface->ReleaseDC(dcSource); return S_OK; } else // common blit allowed between clonable surfaces return m_pObject->Blt(destRect, pSourceSurface, sourceRect, dwFlags, lpDDBltFx); } else return hr; } else return hr; }
呼!!确实是个巧妙的办法……一方面,由于该例程执行拉伸操作,它独立于blit对象的大小,特别是源和目标缓冲区的大小。另一方面,由于该例程还检查颜色深度,并在需要时显式使用GDI的颜色映射,我们可以说该例程与颜色无关。总而言之,这个blit例程与大小和颜色无关。相当有用!!我相信这能帮助一些人。可能很多程序员已经做过这样的包装类了。
现在谈谈alpha混合?首先,我想指出这对我的意义很大,而不是一个精确的概念。将常见的alpha混合支持添加到这个blit例程将使其成为可能的最通用,因此最有用的。这对你来说是个很好的练习。我提醒你,DirectX运行库直到6.1版本都不支持alpha blit,所以很多东西都需要手动处理。可怜的我们!!!我希望DirectX 7.0会带来更好的功能。
3. 一个使用此框架的全屏视频播放器
一篇文章在提供完整的源代码和可执行文件时才开始帮助人们。这篇文章本身似乎只是一个包装,很多时候甚至没有人看。可怜的我。
但由于我不想错过展示有用代码的机会,我在这里提供了一个DirectShow下的全屏视频播放器。
希望您能充分利用它。
该框架用于主表面和两个后备缓冲区。请注意,概念上我们不需要两个后备缓冲区,一个就足够了。但最好为两个后备缓冲区准备代码,这样将来可以轻松添加通用的中间叠加效果等。