Download demo executable - 9 Kb
Download source files - 32 Kb
This article provides the following content :
- A DirectDraw Framework made of template classes
- A versatile blit routine which encompasses all pixel formats available
- As a gift, a full-speed fullscreen video player under DirectShow, using this framework
1. DirectDraw framework through Template classes
It is known that all DirectDraw objects and surfaces are handled through the standard COM method call, ie QI, AddRef (hidden), and Release. The AddRef() call is mostly hidden because any time you query for an interface, the COM component will automatically call AddRef() for you. Thus, while it's also good to remember that the developer should use AddRef() any time a pointer is given by copy, it must be said that having to manage Release() is quite boring. The ATL framework comes with a solution to this, in name of CComPtr and CComQIPtr template classes. Those classes hide Release() calls for us to concentrate on important stuff, rather on that AddRef()/Release() count issue. Pretty good.
Because it is important in this article, what follows is the source code from the ATL library (� Microsoft) for the CComPtr class (#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; }
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
T* p;
};
The article is entitled DirectDraw extensions, so why the hell do I talk about ATL? In fact, the main thing that came to my mind is that I wanted to overcome inherant DirectDraw BLIT limitations. See next section. And the most straight forward solution to add functionalities to an interface that clearly can't be replaced, is to wrap all functionalities in another class. And in this class, calls to the Blit() function will be either delegated to the standard IDirectDrawSurface->Blt() functionality, or will go through our own blit processing.
Why templates? Though the article focuses only on that IDirectDrawSurface interface, because Blit() is made through it only (DirectDraw doesn't support aggregation), in fact the concept of wrapper classes can be generalized to all DirectDraw interfaces : IDirectDraw, IDirectDrawSurface, IDirectDrawClipper, IDirectDrawPalette, not to talk about IDirectDraw2, IDirectDraw3, IDirectDraw4, and so on......In fact, such set of template classes is what I like to call a framework and can be utterly substituted to a standard DirectDraw application written in C++. Not to say that this object also applies to ANY COM interface.
The name of the game is not only to use wrapper template classes as well or instead of standard DirectDraw COM manipulation. The name of the game is to provide a framework which is thought so that those classes integrate seamlessly into "old-style" DirectDraw applications. That's where it comes to be interesting.
Now let me give you my source code for the root template class, and the CDDSurface class.
#include "atlbase.h"
template <class T>
class CDDObject
{
public :
CComPtr<T> m_pObject;
CDDObject()
{
}
~CDDObject()
{
Release();
}
T **operator&() { return &m_pObject; }
operator T*() { return m_pObject; }
BOOL operator!() { return ((T*)m_pObject==NULL) ? TRUE : FALSE; }
HRESULT Release() { m_pObject.Release();
return S_OK; }
};
The couple of operators I have implemented provide in fact easy access to the hidden pointer, and facilitates seamless integration to standard DirectDraw applications. The operators are used for example any time you query an interface (& operator), or if you want to downcast, or again if you want to check your pointer (! operator). At the end of this article, I give a full source code using this class.
#ifndef _CDDSURFACE
#define _CDDSURFACE
#include <ddraw.h>
#include "CDDObject.h"
class CDDSurface : public CDDObject
{
public :
CDDSurface();
~CDDSurface();
HRESULT Blt(RECT *destRect, IDirectDrawSurface *pSourceSurface, RECT *sourceRect,
DWORD dwFlags, LPDDBLTFX lpDDBltFx);
};
#endif
As you can see, the CDDSurface inherits the "core" CDDObject template class of type T=IDirectDrawSurface. This class provides implementation to our blit routine.
Note that only the Blit is delegated, all other standard IDirectDrawSurface functionalities are accessed by an explicit cast such like :
CDDSurface my_surface;
...
HDC my_dc;
((IDirectDrawSurface*)my_surface)->GetDC(&my_dc);
2. A versatile blit routine
In this section we are going to implement the blit routine.
Now what's the matter with that? Let's say to begin that the GDI performs blits through two WIN32 methods, namely ::BitBlit() and ::StretchBlit(). The StretchBlit method is the most general method since it can scale and blit at the same time. But it's slow. When you know by far that you just want to perform a simple copy blit, then BitBlit is faster. Moreover, whether your buffer is a bitmap or a DIB Section, there are a couple of other methods.
In the context of DirectDraw, there's a single BLT method which acts like the ::StretchBlit() method. DirectDraw is much more powerful than the GDI in that HAL/HEL abstraction can take advantage of your hardware.
But, because there is a true drawback, DirectDraw doesn't know how to blit between DirectDraw surfaces not having the same pixel format. Say between a 16-bit surface and a 24-bit surface. Very often, you load a 256 colors bitmap, and then you wish to make it a true DirectDraw surface to be blitted to the primary surface. Since the primary surface has the depth of the desktop, that is 24 or 32 bits mostly, this doesn't work that easy !!
Now for the solution. The GDI not only blits, it also performs color mapping as needed. Thus the idea, remaining trivial as you can see, is to delegate the blit to the DirectDraw HAL/HEL blit engine any time the surfaces are of the same color depth, and delegate the blit to a simple ::StretchBlit from the GDI any time the surfaces are of different color depths.
Enough words, now for the code :
#include "CDDSurface.h"
HRESULT CDDSurface::Blt(RECT *destRect, IDirectDrawSurface *pSourceSurface, RECT *sourceRect,
DWORD dwFlags, LPDDBLTFX lpDDBltFx)
{
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))
{
BOOL bSamePixelFormat=
(ddsdDest.ddpfPixelFormat.dwFlags==ddsdSource.ddpfPixelFormat.dwFlags) &&
(ddsdDest.ddpfPixelFormat.dwRGBBitCount==ddsdSource.ddpfPixelFormat.dwRGBBitCount);
if (!bSamePixelFormat)
{
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
return m_pObject->Blt(destRect, pSourceSurface, sourceRect, dwFlags, lpDDBltFx);
}
else
return hr;
}
else return hr;
}
Pheeww !!! Quite a hack indeed...On one hand, because the routine does perform stretchs, it's independent of the size of what you blit, and especially whether the source and destination buffers have the same size. On the other hand, because the routine also checks color depths and use explicitely color mapping from the GDI any time it is required, we can say that the routine is independent of the color. All in all, this blit routine is independent of size and color. Quite useful !! I believe this can help some people. Probably a lot of coders have already made such wrapper classes.
Now what about alpha blend? First of all, I want to point out that this means quite a lot of things to me, and not a precise thing. Adding commonly known alpha blend support to this blit routine would make it almost the most general ever possible, hence the most useful. It's a good exercise for you to try to do it. Let me remember you that DirectX run-times up to 6.1 don't allow alpha blits, thus most things will have to be carried by hand. Poor us !!! I hope DirectX 7.0 comes with better features.
3. A fullscreen video player using this framework
An article starts helping people only when a full source code and executable is provided. It seems that the article is itself just a wrapper, very often not even read. Poor me.
But because I didn't want to miss the opportunity to show a useful piece of code, I give you here a fullscreen video player under DirectShow.
I hope you make good use of it.
The framework is used for the primary surface and the two backbuffers. Note that conceptually we don't need two backbuffers, one is enough. But it's better to prepare code for two backbuffers so it's easy in the future to add versatile in-between overlay effects and so.