Inicializar DelphiX (2ªparte)
- Capitulo II de la serie "DelphiX 3D" -


Autor  

 Deimos

Ámbito  

 DelphiX / DirectX 7

Lenguaje de Programación  

 Delphi 4

Fecha  

 2002/03/25

    En el anterior tutorial vimos cómo crear una aplicación DirectX con DelphiX y dejarla inicializada para hacer escenas 3D. Sin embargo, dejamos de lado muchas consideraciones que se han de tener en cuenta a la hora de configurar correctamente una aplicación DirectX y permitimos que DelphiX se las apañara solo con los conflictos de configuración que pudieran surgir, lo que, las mas de las veces, es garantía de fracaso.

    En este tutorial vamos a aprender cómo comprobar que la versión de las DirectX instaladas es la correcta, cómo obtener información sobre el hardware, y determinar los modos de video de los que disponemos para asegurarnos de que hemos configurado correctamente la aplicación DelphiX, e incluso seleccionar nosotros mismo la configuración que mas nos guste o que mejor se adapte a la aplicación.

    Aunque nuestro objetivo es desarrollar aplicaciones DelphiX, en esta ocasión vamos a trabjar mas directamente con las APIs ya que detectar la versión de las DirectX usadas ha de ser un paso previo a cualquier tarea que se haga con DelphiX. De nada sirve inicializar nuestra aplicación para usar las DX7 si el usuario no tiene instaladas las DX7 o una versión superior.

    La clave para conseguir los objetivos de este turtorial es la interfaz IDirectDraw7 que incorpora todos, o casi todos, los métodos que necesitamos para configurar correctamente nuestra aplicación. Dicha interfaz, junto con la mayoría de las interfaces de las DX7, se encuentra en el archivo DIRECTX.PAS de las fuentes de DelphiX, versión DelphiX de las API DirectX 7. Si decidimos utilizar las APIs DX7 adaptadas por Erik Unger el archivo en el que encontraremos las mencionadas interfaces es DIRECTDRAW.PAS.

    Para aquellos que pretendan usar este tutorial como guía para programar con las API de las DX8 o superior avisarles que en dichas APIs no encontrarán la interfaz IDirectDraw7, sin embargo la interfaz IDirect3D8 incorpora todos los métodos necesarios para conseguir lo que en este tutorial se explica (lo que no significa que los nombres de los métodos coincidan, de hecho casi nunca coincidirán).

    Empecemos pues :-))


    Lo primero que hemos de decidir, antes de ponernos a teclear código, es si vamos a encargarle a la aplicación que detecte si puede funcionar en el sistema en el que se encuentra o si se lo vamos a encargar a un programa externo.

    Particularmente, y para no sobrecargar el código de la aplicación, soy partidario del uso de programas externos que detecten la configuración del sistema y los modos de vídeo disponibles. Este no será el caso de nuestro ejemplo tutorial pues no tendrá un número excesivo de líneas, pero el código que hagamos vamos a ponerlo en una Unit separada pues, como ya se dijo, está mas relacionado con las APIs DX7 que con DelphiX por lo que podremos usarla con cualquier otra aplicación DX7 que no use DelphiX. Así pues empezaremos creando una nueva aplicación (¡ojo, no ponga controles en ella todavía!) la salvamos con el nombre D3DX7Ini2 para el proyecto y UD3DX7Ini2 para la unit principal, y le añadimos una unit que guardaremos con nombre UDX7Config (si lo prefiere por resultarle mas descriptivo UDX7Setup también sirve); acto seguido añadimos la unit al proyecto y lo salvamos. Hecho esto, en el editor deberíamos ver 2 pestañas con los nombres de las 2 Units del proyecto.

    Dado que IDirectDraw7 está declarada en la unit DirectX  la incluimos en la cláusula uses de la sección interface de UDX7Config.

    La primera tarea a realizar es detectar si las DX instaladas en el sistema son las 7 o una versión superior. Para ello sólo necesitamos una función que devuelva un valor lógico:

function CreaDX7(var lpDDraw : IDirectDraw7) : Boolean;

    Dado que nuestro método para detectar los drivers DX es crear el objeto DirectDraw correspondiente aprovechamos la función para tratar de crear el nuestro. Para ello pasamos como parámetro a la función nuestro interfaz DirectDraw7. Si logra crear la interfaz es que el sistema tiene instaladas las DirectX7 o superior, si falla... "¡Eh pibe! ¿A dónde vas tan rápido?" 8D

    Aunque es un método un poco "bruto" de verificar si el sistema soporta DX7 o no, es mas fiable que echarle una ojeada al registro de windows ya que no se suele actualizar si se hace una desinstalación a lo "bestia" de las DirectX (al final de este tutorial pondré el código de una función que mira en el registro para determinar qué versión de las DirectX están instaladas).

    Una vez comprobado si el sistema soporta DirectX7, el siguiente paso es obtener los modos de video que podemos utilizar con nuestra aplicación.

function ConsigueModosVideo(lpDDraw : IDirectDraw7; lpModos : pointer) : Boolean;

    En lpDDraw le pasamos a la función nuestro interface, y en lpModos el objeto en el que recibiremos la lista de modos de video disponibles. LpModos es de tipo pointer pues nos sirve cualquier control o estructura que consideremos conveniente para recoger la información; en este ejemplo usaremos un TListBox y a la función le daremos como parámetro la propiedad Items. Si se produce algún error al obtener la lista de modos de video la función devolverá False

    Ahora que ya tenemos los modos de video vamos a determinar si tenemos una tarjeta aceleradora 3D o si es necesaria la emulación por software:

function Aceleracion3D(lpDDraw : IDirectDraw7;  lpCaps : pCaps) : Boolean;

    Como en el caso de ConsigeModosVideo el primer parámetro que requiere esta función es nuestra interface. El segundo parámetro es un puntero a la estructura en la que recogeremos los datos sobre las capacidades 3D del sistema. Si se produce algún error la función devuelve False.

    Estas son las funciones que necesitamos para configurar correctamente una aplicación DirectX, así que las declararemos en la sección interface de UDX7Config para hacerlas accesibles.

    Antes de ponernos a teclear el código de estas funciones necesitamos la siguiente función que declaramos e implementamos en la sección implementation:.

function EnumModesCallBack(const lpDDSurfaceDesc : TDDSurfaceDesc2;
                                                  lpContext : Pointer) : HResult; stdcall;
begin
 // Extraemos los datos del modo de pantalla
 TStringList(lpContext).Add(IntToStr(lpDDSurfaceDesc.dwWidth)+'x'+
                            IntToStr(lpDDSurfaceDesc.dwHeight)+' '+
                            IntToStr(lpDDSurfaceDesc.ddpfPixelFormat.dwRGBBitCount)+
                            ' bits/pixel');
 // Proseguir con la enumeración
 Result := DDENUMRET_OK;
end;

    El valor HResult que devuelve es un código de error que se puede utilizar para determinar el mensaje de error a mostrar de haberse producido. Estos códigos están comentados en DIRECTX.PAS. En nuestro caso devolvemos el código DDENUMRET_OK que le indica al sistema que debe seguir con el proceso de enumeración. Huelga todo comentario sobre el resto del código pues considero que el lector está al tanto de lo que es una conversión de tipos o type casting.

    He identificado la función con nombre en inglés pues diferentes versiones de ella se pueden encontrar en los fuentes de los múltiples tutoriales y ejemplos DirectX que existen en la red.

    Metámonos con el código de nuestro ejemplo.

    Lo primero es declarar el tipo que usaremos para recoger los datos sobre el sistema que nos permitirán determinar las capacidades 3D de las que disponemos. Ya que queremos acceder a este tipo desde el programa principal lo declararemos en la sección interface:

type
  pCaps = ^TCaps;
  TCaps = Record
    HAL,
    HEL : PDDCAPS;
  end;

    Lo importante de esta declaración son los punteros HAL y HEL a la estructura DirectX TDDCaps declarada y comentada en el archivo DIRECTX.PAS (DIRECTDRAW.PAS de las API de Eric Unger). HAL y HEL hacen referencia a las capacidades Hardware y a la emulación Software; son las siglas de Hardware Abstraction Layer y Hardware Emulation Layer respectivamente.

    Ha llegado el momento de implementar las funciones declaradas mas arriba:

function CreaDX7(var lpDDraw : IDirectDraw7) : Boolean;
begin
 // Creamos el objeto DirectDraw7
 Result:=DirectDrawCreateEx(Nil, lpDDraw, IID_IDirectDraw7, Nil)=0;
end;

    Como vemos, la función DirectDrawCreateEx es la encargada de crear nuestro objeto DirectDraw y para ello hemos de darle el código identificador del objeto (tercer parámetro), el cual se encuentra declarado en DIRECTX.PAS (DIRECTDRAW.PAS), y nuestro identificador para el objeto DirectDraw; los parámetros primero y cuarto son punteros a un identificador global: PGUID, de objetos COM, y a una interface; dado que no tenemos ningún objeto COM ni deseamos tener una segunda interface, NIL es el valor que hemos de pasarle a la función en estos dos parámetros.

    DirectDrawCreateEx devuelve el correspondiente código de error (HResult) si falla al crear el objeto IDirectDraw buscado o 0 si tiene éxito.

    Si CreaDX7 no devolvió False ya tenemos acceso a la interface IDirectDraw7, luego el sistema tiene, como mínimo, instaladas las DirectX 7. Podemos, pues, recoger información sobre las capacidades del sistema y los modos de video disponibles, lo que hacemos con:

function ObtenModosVideo(lpDDraw : IDirectDraw7; lpModos : pointer) : Boolean;
begin
 // Obtenemos los modos de video
 Result:=lpDDraw.EnumDisplayModes(0, nil, lpModos, EnumModesCallBack)=0;
end;

    El método IDirectDraw7.EnumDisplayModes es el encargado de recabar la información sobre los modos de video soportados por el sistema. Este método no sólo sirve para enumerar los modos de video disponibles sino que puede comprobar si el modo que hemos seleccionado es válido.

    El primer parámetro es un flag de enumeración (ver DIRECTX.PAS) que regula el comportamiento del método.

    El segundo es un puntero a la estructura DDSurfaceDesc2 (de nuevo me remito a DIRECTX.PAS) en la que pondremos los datos del modo video que queremos comprobar si hemos seleccionado uno. Con el valor Nil forzamos que se enumeren todos los modos de video disponibles.

    El tercer parámetro es un puntero a la estructura en la que recogeremos los datos obtenidos por el método.

    El cuarto y último, es la llamada a nuestra función EnumModesCallBack, que pasará los datos de los modos de video a la estructura apuntada por lpModos (o el resultado de la comprobación si estamos verificando un determinado modo de video).

    Con:

function Aceleracion3D(lpDDraw : IDirectDraw7; lpCaps : pCaps) : Boolean;
begin
 // Reseteamos la informacion sobre capacidades
 // HAL (Hardware Abstraction Layer)
 FillChar(TDDCaps(lpCaps.HAL^), SizeOf(TDDCaps), 0);
 TDDCaps(lpCaps.HAL^).dwSize:=SizeOf(TDDCaps);
 // HEL (Hardware Emulation Layer)
 FillChar(TDDCaps(lpCaps.HEL^), SizeOf(TDDCaps), 0);
 TDDCaps(lpCaps.HEL^).dwSize:=SizeOf(TDDCaps);
 // Obtenemos las capacidades Hardware y Software del sistema.
 Result:=lpDDraw.GetCaps(lpCaps.HAL,lpCaps.HEL)=0;
end;

    Aceleracion3D recogemos los datos sobre las capacides Hardware y Software mediante el método IDirectDraw7.GetCaps el cual requiere los punteros a las estructuras TDDCaps a las que pasa los datos que buscamos.

    Antes de llamar a este método ponemos a 0 todos los parámetros de las estructuras HAL y HEL para asegurarnos de que la información recogida es fiable.

    Ahora que ya tenemos hecho el código de la unit, vamos a poner algunos controles en nuestra aplicación para ver que tal trabaja la unit. Empezaremos por el TListBox donde pondremos los datos que se recojan, y un par de botones: uno para controlar la información que se vea en el TListBox, y otro para cerrar la aplicación. En el ejemplo he cambiado el nombre por defecto del TListBox por lbxInfo para que sea mas descriptivo. (NOTA: como regla general conviene poner nombres descriptivos a aquellos controles que hagan funciones importantes dentro de una aplicación a fin de clarificar el código; en aplicaciones con pocos controles no es difícil saber qué hace cada uno, pero cuando hay varios, algunos de ellos del mismo tipo, resulta muy útil ponerles nombres que nos recuerden lo que realmente hacen, lo que a la hora de depurar siempre es una ayuda) El aspecto de nuestra ficha ha de parecerse al de la imagen 1:

(Imagen 1)

    En la sección private de la ficha declararemos las propiedades

SysCaps : pCaps;

en la que recogeremos la información que nos dé la función Aceleracion3D, y:

bModosVideo : Boolean;

flag que controla la información a mostrar en el TListBox; y añadimos el método:

procedure MuestraCaps;

que pondrá en el TListBox la información recogida en SysCaps.

    Los eventos que vamos a activar son:

procedure TForm1.FormCreate(Sender: TObject);
begin
 // Asignamos memoria a los elementos que lo precisen
 Syscaps:=AllocMem(SizeOf(TCaps));
 SysCaps.HAL:=AllocMem(SizeOf(TDDCAPS));
 SysCaps.HEL:=AllocMem(SizeOf(TDDCAPS));
 // Inicializamos propiedades no inicializadas
 bModosVideo:=True;
end;
 
procedure TForm1.FormActivate(Sender: TObject);
begin
 // Inicializa las DirectX 7
 If Not CreaDX7(DDraw7) Then
  begin
   MessageDlg('Error de inicializacion',mtError,[mbOk],0);
   DDraw7:=Nil;
   Close;
   Exit;
  end;
 // Recogemos los datos sobre las capacidades del sistema
 If Not Aceleracion3D(DDraw7,SysCaps) Then
  begin
   MessageDlg('No se han obtenido las capacidades 3D del sistema.',mtError,[mbOk],0);
   DDraw7:=Nil;
   Close;
   exit;
  end;
 // Sacamos a pantalla la información recogida
 Button2Click(Self);
end;
 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 FreeMem(SysCaps.HEL);
 FreeMem(SysCaps.HAL);
 FreeMem(SysCaps);
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
 Close;
end;
 
procedure TForm1.Button2Click(Sender: TObject);
begin
 // Ponemos en lbxInfo los datos sobre los modos de video o de las capacidades del sistema
// en función del valor de bModosVideo
 lbxInfo.Clear;
 If bModosVideo Then
  begin
   lbxInfo.Items.Add('Modos de video soportados');
   lbxInfo.Items.Add('----------------------------------------------');
   If Not ObtenModosVideo(DDraw7,lbxInfo.Items) Then
    begin
     MessageDlg('No se ha obtenido la lista de modos de video.',mtError,[mbOk],0);
     DDRaw7:=Nil;
     Close;
     exit;
    end;
   bModosVideo:=Not bModosVideo;
  end
 Else
  begin
   bModosVideo:=Not bModosVideo;
   MuestraCaps;
  end;
end;

    Aunque los objetos DirectX creados se autodestruyen liberando la memoria que ocupaban al cerrarse la aplicación, hemos de liberar la memoria asignada a SysCaps pues esta estructura la hemos creado nosotros y, aunque relacionada con estructuras DirectX, DirectX no libera la memoria que le asignemos.

    Creo que el resto del código no merece mayor comentario.

    Ya sólo queda por implementar el método MuestraCaps:

procedure TForm1.MuestraCaps;
const
 Separador = '--------------------------------------------------';
begin
 // Memoria de video disponible
 lbxInfo.items.Add('Memoria de video : '+
                   IntToStr(SysCaps.HAL.dwVidMemTotal div 1000)+
                   ' KB');
 lbxInfo.Items.Add(Separador);
 lbxInfo.Items.Add('');
 // Capacidades aceleradoras del la tarjeta de video (las mas importantes)
 lbxInfo.Items.Add('Capacidades Hardware');
 lbxInfo.Items.Add(Separador);
 // Aceleración 3D
 If (SysCaps.HAL.dwCaps And DDCAPS_3D)=DDCAPS_3D Then
  lbxInfo.Items.Add('Aceleradora 3D - > SI')
 Else
  lbxInfo.Items.Add('Aceleradora 3D -> NO');
 // Tarjeta AGP
 IF (SysCaps.HAL.dwCaps2 And DDCAPS2_NONLOCALVIDMEM)=
DDCAPS2_NONLOCALVIDMEM Then
  lbxInfo.Items.Add('AGP -> SI')
 Else
  lbxInfo.Items.Add('AGP -> NO');
 // Soporte para Z-Buffer
 If SysCaps.HEL.dwZBufferBitDepths > 0 Then
  lbxInfo.Items.Add('Z-Buffer -> SI')
 Else
  lbxInfo.Items.Add('Z-Buffer -> NO');
 // Asincronía
 If (SysCaps.HAL.dwCaps And DDCAPS_BLTSTRETCH)=DDCAPS_BLTSTRETCH Then
  lbxInfo.Items.Add('BitBlit Asincrono -> SI')
 Else
  lbxInfo.Items.Add('BitBlit Asincrono -> NO');
 // Manejo de color
 If (SysCaps.HAL.dwCKeyCaps And DDCKEYCAPS_SRCBLT)=DDCKEYCAPS_SRCBLT Then
  lbxInfo.Items.Add('Uso de color acelerado por Hardware -> SI')
 Else
  lbxInfo.Items.Add('Uso de color acelerado por Hardware -> NO');
 // Soporte para modo ventana
 If (SysCaps.HAL.dwCaps2 And DDCAPS2_CANRENDERWINDOWED)=
DDCAPS2_CANRENDERWINDOWED Then
  lbxInfo.Items.Add('Soporte para modo ventana -> SI')
 Else
  lbxInfo.Items.Add('Soporte para modo ventana -> NO');
 // Hardware certificado
 If (SysCaps.HAL.dwCaps And DDCAPS2_CERTIFIED)=DDCAPS2_CERTIFIED Then
  lbxInfo.Items.Add('Certificado Microsoft -> SI')
 Else
  lbxInfo.Items.Add('Certificado Microsoft -> NO');
 lbxInfo.Items.Add('');
 // Emulación por software (las mas importantes)
 lbxInfo.Items.Add('Emulacion por software');
 lbxInfo.Items.Add(Separador);
 // Aceleración 3D
 If (SysCaps.HEL.dwCaps And DDCAPS_3D)=DDCAPS_3D Then
  lbxInfo.Items.Add('3D - > SI')
 Else
  lbxInfo.Items.Add('3D -> NO');
 // Tarjeta AGP
 IF (SysCaps.HEL.dwCaps2 And DDCAPS2_NONLOCALVIDMEM)=
DDCAPS2_NONLOCALVIDMEM Then
  lbxInfo.Items.Add('AGP -> SI')
 Else
  lbxInfo.Items.Add('AGP -> NO');
 // Soporte para Z-Buffer
 If SysCaps.HEL.dwZBufferBitDepths > 0 Then
  lbxInfo.Items.Add('Z-Buffer -> SI')
 Else
  lbxInfo.Items.Add('Z-Buffer -> NO');
 // Asincronía
 If (SysCaps.HEL.dwCaps And DDCAPS_BLTSTRETCH)=DDCAPS_BLTSTRETCH Then
  lbxInfo.Items.Add('BitBlit Asincrono -> SI')
 Else
  lbxInfo.Items.Add('BitBlit Asincrono -> NO');
 // Manejo de color
 If (SysCaps.HEL.dwCKeyCaps And DDCKEYCAPS_SRCBLT)=DDCKEYCAPS_SRCBLT Then
  lbxInfo.Items.Add('Uso de aceleración de color -> SI')
 Else
  lbxInfo.Items.Add('Uso de aceleración de color -> NO');
 // Soporte para modo ventana
 If (SysCaps.HEL.dwCaps2 And DDCAPS2_CANRENDERWINDOWED)=
DDCAPS2_CANRENDERWINDOWED Then
  lbxInfo.Items.Add('Soporte para modo ventana -> SI')
 Else
  lbxInfo.Items.Add('Soporte para modo ventana -> NO');
 // Hardware certificado
 If (SysCaps.HEL.dwCaps And DDCAPS2_CERTIFIED)=DDCAPS2_CERTIFIED Then
  lbxInfo.Items.Add('Certificado Microsoft -> SI')
 Else
  lbxInfo.Items.Add('Certificado Microsoft -> NO');
end;

    Este método sólo es un ejemplo de lo que se puede extraer, y de cómo extraerlo, de la información recogida en SysCaps.HAL y SysCaps.HEL. De nuevo he de remitirme a DIRECTX.PAS ya que ahí tenemos un comentario bastante exacto de lo que es cada campo de la estructura TDDCaps y de las constantes DDCAPS_, DDCAPS2_, DDCKEYCAPS_, y otras que se utilizan para extraer los datos codificados en TDDCaps. Por ejemplo, nosotros sólo miramos si el sistema soporta el Z-Buffer comprobando si el valor de dwZBufferBitDepths es mayor que 0, sin embargo podemos saber el tipo de Z-Buffer comparándolo con las constantes:

DDBD_1   ->   1 bit  por pixel
DDBD_2   ->   2 bits por pixel
DDBD_4   ->   4 bits por pixel
DDBD_8   ->   8 bits por pixel
DDBD_16 -> 16 bits por pixel
DDBD_24 -> 24 bits por pixel
DDBD_32 -> 32 bits por pixel

dejo al lector que haga los pertinentes cambios para que la aplicación refleje el tipo de ZBuffer soportado si existe.


ANEXO

    Lo prometido es deuda. Aquí tenemos la función que nos da las DirectX instaladas leyendo el resgistro de windows. Para que funcione correctamente es fundamental incluir la unit Registry en la correspondiente cláusula uses.

function VerDirectX : string;
var
 Registro : TRegistry;
begin
 Registro:=TRegistry.Create;
 Try
  Registro.RootKey:=HKey_Local_Machine;
  If Registro.OpenKey('\Software\Microsoft\DirectX', False) Then
   Result:=FRegFile.ReadString('Version');
  Else
   Result:="No se encontró información";
 Finally
   Registro.CloseKey;
   Registro.Free;
 end;
end;
 

Nota final:

    Recientemente he descubierto una discrepancia entre los fuentes de DIRECTX.PAS y DIRECTDRAW.PAS que atribuyo a un error en la adaptación de las APIs en DIRECTX.PAS. Dado que este ejemplo tutorial trabaja directamente con las API y no utiliza los controles DELPHIX he decidido usar DIRECTDRAW.PAS en lugar de DIRECTX.PAS. Si el lector no dispone de la librería DirectX 7 adaptada por Erick Unger puede encontrarla en

http://www.crazyentertainment.net/olddxdownloads.htm

o en:

http://www.delphi-jedi.org/DelphiGraphics/oldindex.htm


Descargar el Código Fuente de Ejemplo


 

 
Hosted by www.Geocities.ws

1