65.9K
CodeProject 正在变化。 阅读更多。
Home

DirectDraw 扩展

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (2投票s)

1999年11月28日

6分钟阅读

viewsIcon

167589

downloadIcon

2995

一个由模板类组成的 DirectDraw 框架。

  • 下载演示可执行文件 - 9 Kb
  • 下载源文件 - 32 Kb
  • 本文提供以下内容

    • 一个由模板类组成的 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下的全屏视频播放器。

    希望您能充分利用它。

    该框架用于主表面和两个后备缓冲区。请注意,概念上我们不需要两个后备缓冲区,一个就足够了。但最好为两个后备缓冲区准备代码,这样将来可以轻松添加通用的中间叠加效果等。

    © . All rights reserved.