====== DirectDraw ======
DirectDraw es un manejador de memoria de vídeo con el cual puedes manipular directamente, desde esta, imágenes y mostrarlas de forma eficiente por pantalla.
===== Inicialización y configuración =====
==== Comenzando ====
* Para poder empezar a desarrollar con DirectDraw mediante C++ necesitaremos linkar la librería ''ddraw.lib'' e incluir en nuestro código la cabecera ''ddraw.h''. Como vamos a trabajar con DirectDraw7, la última versión que se desarrolló, necesitaremos agregar también ''dxguid.lib''.
* El WinMain de la aplicación será el típico, podremos empezar a configurar el DDraw justo después de hacer las llamadas a ''ShowWindow'' y\o ''UpdateWindow''.
* **Definiciones**:
* //Superficie (Surface)//: Se refiere a zonas de memoria de vídeo, estas son las que contienen las imágenes que queremos mostrar en nuestra aplicación.
* **Tipos**:
* //LPDIRECTDRAW7//: Objeto DirectDraw, es un //IDirectDraw//. Contiene métodos útiles para:
* Gestión del nivel cooperativo: SetCooperativeLevel o TestCooperativeLevel.
* Gestión de modos de ventana: EnumDisplayModes, GetDisplayMode, GetMonitorFrequency, RestoreDisplayMode o SetDisplayMode.
* Gestión de superficies: DuplicateSurface, EnumSurfaces, GetGDISurface, GetSurfaceFromDC o RestoreAllSurfaces.
* Creación de objetos: CreateClipper, CreatePalette o CreateSurface.
* //LPDIRECTDRAWSURFACE7//: Surface de DirectDraw.
==== Inicialización ====
Para configurar e iniciar la programación con DirectDraw tendremos que seguir los siguientes pasos, cada uno de ellos se basan en llamadas a funciones que devuelven una flag que si es distinta de ''DD_OK'' significa que ese paso ha fallado. ''DD_OK'' es del tipo ''HRESULT''.
- **Crear un objeto DirectDraw**. Utilizaremos la función ''DirectDrawCreateEx'' pasándole 4 parámetros:
* El GUID de la gráfica, esto es su identificador (si lo pasamos como NULL será el por defecto, aunque también podemos pasar las siguientes flags: DDCREATE_EMULATIONONLY, para modo emulado, y DDCREATE_HARDWAREONLY, sin emulación (DirectDraw no emula lo que no existe en hardware)).
* Referencia al LPDIRECTDRAW7 (objeto DirectDraw) que crearemos, debe ser casteado como ''(void**)''.
* ''IID_IDirectDraw7'', que es un identificador de clase. :?:
* Un valor NULL (realmente es para compativilidades futuras con objetos COM, pero no se utiliza).
- **Configurar el modo coperativo**. Esto determina cómo interactuaremos con el hardware. Para realizar esta configuración, el objeto DDraw contiene un método llamado ''SetCooperativeLevel'' al cual le pasas el HWND de la ventana y los flags de configuración, estos son:
* DDSCL_EXCLUSIVE, que hace que únicamente DDraw se encargue de la gestión de gráficos, excluyendo GDI. Se ha de utilizar con DDSCL_FULLSCREEN.
* DDSCL_FULLSCREEN, hace que la ventana se muestre a pantalla completa. Se ha de utilizar con DDSCL_EXCLUSIVE.
* DDSCL_NORMAL, hace que DDraw se gestione en modo ventana.
* DDSCL_ALLOWREBOOT, permite al usuario realizar un ctrl+alt+supr cuando ejecute tu aplicación.
- **Configurar el modo de pantalla**. Esto es indicar resolución y profundidad de color, si retorna DDERR_INVALIDMODE significa que la máquina que corre el programa no soporta este modo. Puedes utilizar el método del objeto DDraw ''EnumDisplayModes'' para ver los soportados.
#include
...
#define MSGBOX(body) MessageBox(NULL,body,"",MB_OK);
...
LPDIRECTDRAW7 lpdd;
...
if (DirectDrawCreateEx(NULL, (void**)&lpdd, IID_IDirectDraw7, NULL) != DD_OK)
MSGBOX("Falla la creación de DDraw!");
if (lpdd->SetCooperativeLevel (hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN) != DD_OK)
MSGBOX("Falla el cambio de modo a cooperativo!");
if (lpdd->SetDisplayMode (640, 480, 32, 0, 0) != DD_OK)
MSGBOX("Falla la asignación del modo de pantalla!");
==== Creando superficies ====
Existen dos tipos de superficies que una aplicación que implemente doble buffer con DDraw debe tener:
* Superficie primaria: Es la superficie que el usuario vé.
* Superficie no visible (backbuffer): Es la superficie que se utiliza para realizar del doble buffer, se dibuja sobre esta para luego intercambiarla por la primaria, mientras, esta no es visible. \\
Recuerda, una superficie es del tipo //LPDIRECTDRAWSURFACE7//.
LPDIRECTDRAWSURFACE7 primarySurface;
LPDIRECTDRAWSURFACE7 backSurface;
Una vez tengamos definidas los objetos correspondientes a las superficies de DDraw necesitaremos configurarlos:
- Definiremos un //DDSURFACEDESC2// y lo configuraremos indicando su tamaño (//dwSize//), las flags de configuración en la propiedad //dwFlags// (DDSD_CAPS y DDSD_BACKBUFFERCOUNT indican que se usará backbuffer y para indicar las capacidades de la superficie una estructura DDSCAPS), si indicamos que utilizaremos un DDSCAPS también tendremos que indicar las flags para esta que es más de lo mismo para definir la superficie (recomendadas: DDSCAPS_PRIMARYSURFACE (indica que es la superfice primaria), DDSCAPS_FLIP (le dá la capacidad de flipping (intercambio de buffers)) y DDSCAPS_COMPLEX (superficie raíz)) y mediante la propiedad //dwBackBufferCount// indicamos que sólo tendrá un backbuffer, pero podemos poner más.
- Añadiremos el //DDSURFACEDESC2// a la superficie principal mediante el método de esta ''CreateSurface''.
- Definiremos un //DDSCAPS2// para asignarselo a la superficie del BackBuffer. Lo configuraremos indicando en su propiedad //dwCaps// la flag DDSCAPS_BACKBUFFER.
- Asignaremos el anterior //DDSCAPS2// a la superficie del BackBuffer y esta la asignaremos a la principal como tal mediante la función de la superficie primaria ''GetAttachedSurface''.
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
if (lpdd->CreateSurface (&ddsd, &primarySurface, NULL) != DD_OK)
MSGBOX ("Falla la creación de la superficie principal");
DDSCAPS2 ddscaps;
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
if (primarySurface->GetAttachedSurface(&ddscaps, &backSurface) != DD_OK)
MSGBOX ("Falla el enlace con el backbuffer");
* ''ZeroMemory'' limpia la memoria para //DDSURFACEDESC2//, de esa forma no tendremos problemas más tarde.
==== Cerrando DirectDraw ====
Una vez acabemos una aplicación de DDraw debemos dejarlo todo tal cual estaba, para ello seguiremos los siguientes pasos:
- Dejar el modo de pantalla como estaba antes llamando al método ''RestoreDisplayMode'' del objeto DirectDraw.
- Dejar el nivel cooperativo en normal volviendo a utilizar el método ''SetCooperativeLevel'' pero esta vez pasándole la flag ''DDSCL_NORMAL''.
- Liberar la superficie primaria utilizando su método ''Release''.
- Liberar las demás superficies utilizadas para cargar imágenes (método ''Release'').
- Liberar el objeto DirectDraw también mediante su método ''Release''.
lpdd->RestoreDisplayMode();
lpdd->SetCooperativeLevel(hwnd, DDSCL_NORMAL);
primarySurface->Release();
lpdd->Release();
==== Cuando se pierden las superficies... ====
Cuando se pierden las superficies... Hay que recuperarlas. \\
Esto ocurre, por ejemplo, cuando el usuario cambia de aplicación mediante un alt+tab o apretando a la tecla de windows. La aplicación pasa a segundo plano y al retornar GDI vuelve a quedarse con el dominio de la ventana, se han borrado las superficies! Cuando esto ocurre los métodos ''Flip'' o ''BltFast'' ya no retornan //DD_OK// sino //DDERR_SURFACELOST//. Aún así podemos recuperarlas fácilmente: únicamente se ha de llamar al método ''Restore'' de cada superficie (o si lo preferimos al ''RestoreAllSurfaces'' del objeto DDraw), pero __al recuperarlas ya no tienen cargadas las imágenes, tendremos que volver a cargarlas de nuevo.__
* Cuando se vuelve a activar la ventana a la aplicación le llega el mensaje ''WM_ACTIVATEAPP''.
===== Haciendo cosillas =====
==== Mostrar una imágen ====
Para DDraw una imágen también es una superficie, por lo que los pasos para mostrar una es cargarla y luego volcarla sobre un objeto //LPDIRECTDRAWSURFACE//. Algo fácil si seguimos los siguientes pasos:
* Crearemos un objeto //BITMAP// desde la dirección de la imágen elegida. Las funciones para hacer esto son propias de GDI.
BITMAP bmp;
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, pathBMP, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
GetObject(hBitmap, sizeof(bmp), &bmp);
* Iniciaremos la superficie donde se volcará el bitmap, primero configuramos su descripción (algo muy parecido a crear una superficie primaria pero teniendo como nuevo la indicación de que asignaremos el alto y el ancho (flags: DDSD_HEIGHT y DDSD_WIDTH)), también indicamos que es una superfie offscreen, esto es plana, esto es que no es una textura, un buffer... Sino un bitmap. Y como decimos que indicaremos el tamaño... pues indicamos también el tamaño mediante ''dwWidth'' y ''dwHeight''.
LPDIRECTDRAWSURFACE7 bmpSurface;
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = bmp.bmWidth;
ddsd.dwHeight = bmp.bmHeight;
lpdd->CreateSurface(&ddsd, &bmpSurface, NULL);
* Copiamos la imágen sobre la superficie, para ello utilizamos la función de GDI BitBlt.
HDC hdcImagen = CreateCompatibleDC(NULL);
HDC hdc;
HBITMAP hBitmapAnt = (HBITMAP)SelectObject(hdcImagen, hBitmap);
bmpSurface->GetDC(&hdc);
BitBlt(hdc, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcImagen, 0, 0, SRCCOPY);
bmpSurface->ReleaseDC(hdc);
SelectObject(hdcImagen, hBitmapAnt);
DeleteDC(hdcImagen);
DeleteObject(hBitmap);
* Para volcar la superficie sobre el backbuffer debemos indicar el tamaño y posición de esta sobre el backbuffer. Para ello se utiliza la estructura //RECTANGLE// (también de GDI).
RECT rectOrig;
rectOrig.left = 0;
rectOrig.top = 0;
rectOrig.right = 640;
rectOrig.bottom = 480;
* El paso final es el de volcar sobre el backbuffer la imágen y el backbuffer sobre la superficie principal. Para lo primero se utiliza el método BltFast de la superficie del backbuffer (pasándole la superficie del bmp y el rectángulo). Y para pasar del backbuffer a la principal (flipping) utilizamos el método Flip de esta.
backSurface->BltFast(0, 0, bmpSurface, &rectOrig, DDBLTFAST_WAIT);
primarySurface->Flip(backSurface, DDFLIP_WAIT);
==== Utilizar funciones de GDI ====
Una vez tenemos las superficies bien montaditas y configuraditas podemos utilizar las funciones de GDI para escribir sobre ellas, es decir, podemos escribir textos, dibujar y todas esas cosas tan monas que hacemos sobre GDI pero esta vez sobre las superficies de DDraw. \\
* Tal vez queramos dibujar sobre el backbuffer y luego volcar este en la superficie principal, para ello se utiliza el método de la superficie principal ''Flip'' (sí, sí... como el saltamontes de la abeja Maya):
primarySurface->Flip(backSurface, DDFLIP_WAIT);
* Para utilizar las funciones de GDI necesitamos tener el HDC, para ello una superficie tiene el método ''getHDC'' al cual le pasas un puntero de HDC y en él te vuelca el HDC deseado.
HDC hdc;
if (Flip(backSurface->GetDC(&hdc) == DD_OK) {
SetBkColor(hdc, RGB(0, 0, 255));
SetTextColor(hdc, RGB(0, 0, 0));
TextOut(hdc, 0, 0, "hola", lstrlen("hola"));
Flip(backSurface->ReleaseDC(hdc);
}
==== Color Keying ====
El color keying es el concepto de indicar a DDraw que en toda una imágen un determinado color funcionará como transparente. De esa forma, cuando nosotros mostremos dicha imágen, las partes con ese color no se veran. \\
Para poder realizar el color keying es necesario un objeto del tipo //DDCOLORKEY// y dar a este su valor bajo (''dwcolorSpaceLowvalue'') y valor alto (''dwcolorSpaceHighValue''), que serán utilizados como segun lo asignemos a la superficie. Para ello, asignarlo, llamamos al método: ''SetColorKey'' pasandole los flags deseados y un puntero al //DDCOLORKEY//.
* **Flags de ''SetColorKey''**:
* //DDCKEY_COLORSPACE//, indica que el objeto no contiene un solo color sino un rango.
* //DDCKEY_SRCBLT//, indica que el color asignado será el utilizado como transparencia.
Por ejemplo, para asignar al verde una transparencia:
DDCOLORKEY ddck;
ddck.dwColorSpaceHighValue = 0x00FF00;
ddck.dwColorSpaceLowValue = 0x00FF00;
bmpSurface2->SetColorKey(DDCKEY_SRCBLT, &ddck);
No debemos olvidar ahora que al volcar la imágen sobre la superficie se ha de indicar que se tiene que tener en cuenta el color keying:
backSurface->BltFast(340, i, bmpSurface2, &rectOrig2, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
=== Asignar el color key en la descripción de la superficie ===
Hacer esto no es tarea compleja, únicamente debemos añadir dos valores más a las propiedades del //DDSURFACEDESC2//, la ''ddckCKSrcBlt.dwColorSpaceLowValue'' y la ''ddckCKSrcBlt.dwColorSpaceHighValue'' correspondientes a los de un //DDCOLORKEY//. También tendremos que indicar mediante una flag más (//DDSD_CKSRCBLT//) que se tenga en cuenta este color key.
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_CKSRCBLT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = bmp.bmWidth;
ddsd.dwHeight = bmp.bmHeight;
ddsd.ddckCKSrcBlt.dwColorSpaceLowValue = 0x00ff00;
ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = 0x00ff00;
==== Blitting ====
El blitter es la parte de la gráfica que puede manipular los datos de las imágenes, para acceder a él mediante DirectDraw se utilizan los método ''Blt'' y ''BltFast'' de una superficie. Con el primero, ''Blt'', puedes realizar más operaciones que con ''BltFast'' pero también es más lento, veamos qué parámetros necesita:
* Un RECT correspondiente a la región de la superficie donde se realizarán las operaciones (destino), si a este parámetro le asignas NULL la regió será toda la superficie.
* Una superficie, el orígen de los datos (por si vas a copiar bits o lo que sea); si no necesitas ninguna porque, por ejemplo, vas a rellenar la superficie destino (en la que llamas a ''Blt'') puedes poner un NULL.
* Otro RECT, este indica la región de la superficie de donde se cogerán los datos (orígen).
* Flags que indican cómo tratar la estructura //DDBLTFX// del siguiente parámetro. Estos pueden ser:
* //DDBLT_COLORFILL//, que indica que se rellenará de un color dado en la propiedad ''dwFillColor'' del //DDBLTFX//.
* //DDBLT_DDFX// para utilizar la propiedad ''dwDDFX'' de la //DDBLFFX// para indicar los efectos que se tendrán en cuenta.
* //DDBLT_DDROPS// y\o //DDBLT_ROP// para utilizar la ''dwDDROPS'' para operaciones de raster.
* //DDBLT_KEYDEST// para usar el color key de la superficie destino.
* //DDBLT_KEYSRC// para utilizar el color key de la superficie orígen.
* //DDBLT_WAIT// la función espera hasta que el blitter esté disponible en vez de dar un error.
* //DDBLT_ROTATIONANGLE// tiene en cuenta la propiedad ''dwRotationAngle'' para indicar el ángulo de rotación.
* El //DDBLTFX// es una estructura para pasar parámetros al ''Blt'', sus propiedades más curiosas (:!: Hay problemas con la rotación):
* ''dwSize'': Tamaño (dado mediante el sizeof) de la estructura.
* ''dwDDFX'': Flags para aplicar efectos de blitting
* Invertir: //DDBLTFX_MIRRORUPDOWN// o //DDBLTFX_MIRRORLEFTRIGHT//.
* Rotar: //DDBLTFX_ROTATE180//, //DDBLTFX_ROTATE270// o //DDBLTFX_ROTATE90//.
* ''dwRotationAngle'', ''dwROP''
* ''dwFillColor'' para rellenar de un color sólido.
* ''ddckDestColorKey'' y ''ddckSrcColorKey'' para asignar color keys. Estos son del tipo //DDCOLORKEY//.
* Por ejemplo, si queremos rellenar una suficie de azul:
DDBLTFX l;
ZeroMemory(&l, sizeof(l));
l.dwSize = sizeof(l);
l.dwFillColor = 0x0000ff;
backSurface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL, &l);
* Si queremos hacer una copia de una superficie:
backSurface->Blt(NULL, bmpSurface, NULL, DDBLT_WAIT, NULL);
* Dibujar en la posición 50, 50 de backSurface y hasta la 100, 100 los píxels (0,0) a (100,100) de bmpSurface (aquí se hace un **escalado** de la imágen).
RECT rectOrig2, rectOrig3;
rectOrig2.left = 50;
rectOrig2.top = 50;
rectOrig2.right = 100;
rectOrig2.bottom = 100;
rectOrig3.left = 0;
rectOrig3.top = 0;
rectOrig3.right = 100;
rectOrig3.bottom = 100;
backSurface->Blt(&rectOrig2, bmpSurface, &rectOrig3, DDBLT_WAIT, NULL);
* Pasándole un //DDBLTFX//...
DDBLTFX l;
ZeroMemory (&l, sizeof(l));
l.dwSize = sizeof(l);
RECT rectOrig2;
rectOrig2.left = 0;
rectOrig2.top = 0;
rectOrig2.right = 100;
rectOrig2.bottom = 100;
backSurface->Blt(&rectOrig2, bmpSurface2, NULL, DDBLT_WAIT, &l);
* Invertir en horizontal la imágen:
// Basándonos en el código anterior
l.dwDDFX = DDBLTFX_MIRRORLEFTRIGHT;
backSurface->Blt(&rectOrig2, bmpSurface2, NULL, DDBLT_WAIT | DDBLT_DDFX, &l);
* Indicar un color key:
// Basándonos en el código anterior
DDCOLORKEY ddck;
ddck.dwColorSpaceHighValue = 0x00FF00;
ddck.dwColorSpaceLowValue = 0x00FF00;
l.ddckSrcColorkey = ddck;
backSurface->Blt(&rectOrig2, bmpSurface2, NULL, DDBLT_WAIT | DDBLT_KEYSRC, &l);
El ''BltFast'' sirve también para copiar de una superficie a otra pero lo hace de forma más rápida que el ''Blt'' y sin aplicar efectos. Sus parámetros son los siguientes:
* Dos integers, para indicar la posición xy de dónde se copiará la imágen (sin escalado).
* La superficie orígen.
* El rectángulo que indica la porción de la superficie orígen a copiar.
* El conjunto deseado de las siguientes flags: //DDBLTFAST_WAIT//, //DDBLTFAST_SRCCOLORKEY// (utilizar color key del orígen), //DDBLTFAST_DESTCOLORKEY// (utilizar color key del destino) y //DDBLTFAST_NOCOLORKEY// (sin color key).
==== Crear una animación ====
El truco para crear una animación de sprites está en el rectángulo que indica la zona de la superficie que se dibujará. Cuando tenemos una superficie con un imágen cargada tenemos que indicar al método ''BltFast'' de la superficie donde queramos cargarla un rectángulo y la posición donde se dibujará. La gracia es que el rectángulo vaya moviendose por una gran imágen donde esten los frames de la animación del sprite pero dibujándose siempre en el mismo sitio. \\
Por ejemplo, imaginemos una imágen que contiene 4 dibujos de una célula, estos corresponden a una rotación donde llega hacer una vuelta completa en estos 4 dibujos\frames. La imágen es de 400x100 (donde cada célula ocupa 100x100). Modificaremos el rectángulo de la siguiente forma:
RECT rectOrig;
int y = 0;
...
rectOrig.left = y;
rectOrig.top = 0;
rectOrig.right = y + 100;
rectOrig.bottom = 100;
Y cada vez que movamos la célula haremos:
y = (y == 300) ? 0 : y + 100;
Pero siempre la dibujaremos en el mismo sitio:
backSurface->BltFast(0, 0, bmpSurface, &rectOrig, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
==== Clipping ====
Esto es hacer que en una superficie sólo se muestre una porción, sería algo así como aplicar una máscara a esta. Para poder realizar esto necesitamos un objeto //LPDIRECTDRAWCLIPPER// que se crea mediante el método ''CreateClipper'' de un objeto //LPDIRECTDRAW7//, a este hay que pasarle 3 argumentos, pero únicamente el 2º es importante, que es un puntero a un objeto //LPDIRECTDRAWCLIPPER// (el primero se pasará como 0 y el tercero como NULL).
LPDIRECTDRAWCLIPPER clipper;
lpdd->CreateClipper(0, &clipper, NULL);
Una vez tengas el objeto necesitarás asignarle una lista de regiones visibles, esto se hace mediante la estructura //RGNDATA//, esta contiene dos elementos: Buffer (donde se guardarán los RECTs utilizados para indicar las regiones) y una estructura ''RGNDATAHEADER'' que indica cómo se debe tratar ese Buffer. Reservaremos el tamaño del buffer con ''malloc'' (y no nos olvidaremos de liberar esa porción de memoria, verdad?) ya que es un array y una vez que asignemos las regiones deberemos hacerlo de arriba abajo. \\
La ''RGNDATAHEADER'' contiene las siguientes propiedades útiles:
* ''dwSize'', el tamaño.
* ''iType'', el tipo de regiones, utilizaremos: //RDH_RECTANGLES//.
* ''nCount'', int, número de regiones.
* ''nRgnSize'', tamaño de las regiones, como utilizaremos RECTs le pasaremos algo parecido a esto: ''sizeof(RECT) * nCount''.
* ''rcBound'', la superficie donde se aplicará el clipping.
LPRGNDATA lpClipList = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER) + sizeof(RECT));
RECT rcClipRect = {300,300,640,480};
memcpy(lpClipList->Buffer, &rcClipRect, sizeof(RECT));
lpClipList->rdh.dwSize = sizeof(RGNDATAHEADER);
lpClipList->rdh.iType = RDH_RECTANGLES;
lpClipList->rdh.nCount = 1;
lpClipList->rdh.nRgnSize = sizeof(RECT);
lpClipList->rdh.rcBound = rcClipRect;
Una vez tengamos la ClipList tendremos que asignarla al clipper mediante el método de este ''SetClipList''. Y para finalizar asignar el clipper a la superficie deseada con el método de la superficie ''SetClipper''.
clipper->SetClipList(lpClipList, 0));
backSurface->SetClipper(clipper));
free(lpClipList);
:!: No funciona del todo bien
===== Notas =====
* Puedes realizar operaciones de blitter sin tener que utilizar ''Blt'' o ''BltFast'', para ello existen funciones de más bajo nivel y con las que tendrás acceso absoluto a los datos de una superficie. Estos son: ''Lock'' y ''Unlock'' (para bloquearla) y ''lPitch'' y ''lpSurface'' (del //DDSURFACEDESC2//) para acceder a ellas.
* En vez de mirar si una función de DDraw devuelve DD_OK puedes utilizar la macro FAILED, que también comprueba el retorno:
if (FAILED(clipper->SetClipList(lpClipList, 0))) ...
* Cuando quieras que la aplicación sea en vez de a pantalla completa en modo ventana tendrás que tener en cuenta unas cuantas cosas:
* Es aconsejable utilizar clipping.
* No puedes utilizar flipping con el backbuffer, si quieres implementar doble buffer tendrás que emularlo mediante otra superficie separada en modo //DDSCAPS_OFFSCREENPLAIN// y haciendo blitting de esta a la principal.
* No hay que cambiar el modo de pantalla.
* El nivel cooperativo pasa a ser únicamente //DDSCL_NORMAL//.
===== Documentos =====
* {{fw:ddraw:ddraw_old.rar|Tutorial y archivos de DirectDraw}}
* {{fw:ddraw:directdraw_programming_tutorial.pdf|Tutorial básico de DirectDraw}}
* [[http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/ddraw7/directdraw7/ddover_7dx3.asp|Documentación de DirectDraw7 (MSDN)]]