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


Autor  

 Deimos

Ámbito  

 DelphiX / DirectX 7

Lenguaje de Programación  

 Delphi 4

Fecha  

 2002/04/13

En este tutorial volvemos a inicializar la aplicación 3D que hicimos en el primer tutorial, pero utilizando la información que recojamos con las funciones implementadas en el segundo tutorial, es decir, vamos a combinar lo hecho hasta ahora para configurar correctamente la aplicación y evitar errores que la bloqueen o, peor aún, bloqueen todo el sistema.

De nuevo recordar que asumo que el lector tiene unos mínimos conocimientos de Delphi, así como de programación Windows, por lo que no me detendré a explicar o comentar aquello que no esté directamente relacionado con DirectX o DelphiX, salvo en los casos que considere necesario hacer dicho comentario, por spuesto 8D.

Empecemos pues.


Aunque nuestro ejemplo es "sencillo", sólo le cambia el color al fondo de la aplicación y pone un texto con los FPS y el nombre del ejemplo, empieza a complicarse por lo que antes de ponernos a teclear hay que hacer un esquema de lo que vamos a hacer.

1) Detectar las DirectX instaladas.
2) Recoger la información sobre modos de video y capacidades 3D.
3) Configurar e inicializar la aplicación DelphiX en función de los datos recogidos.
4) Crear la escena y ejecutarla.

De los pasos 1) y 2) se encargará el código explicado en el tutorial 2.

En 3) modificaramos las propiedades del componente DXDraw para configurarlo en función de los datos recogidos.

En 4) usaremos el código del tutorial 1, pero vamos a añadir un par de cosillas nuevas, por aquello de darle mas vidilla al ejemplo 8D

Ahora que ya tenemos claro lo que hemos de hacer, creamos un nuevo projecto, le añadimos una segunda ficha que será nuestra unit de nombre UDX7Config (dado que esta ficha va a ser un diálogo podemos usar el asistente de creación diálogos) y guardamos el proyecto con los siguientes nombres: D3DX7Ini3.dpr y UD3DX7Ini3.pas. Seguidamente ponemos en la ficha principal los controles TDXDraw y TDXTimer, tal y como hicimos en el primer tutorial, pero en esta ocasión no nos vamos a preocupar por las opciones que tenga TDXDraw ya que las vamos a comprobar y a reconfigurar en tiempo de ejecución. Ahora nos aseguramos de tener en la cláusula uses de UD3DX7Ini3 las units DirectX, DXDraws, y DXClass. y DirectX en el uses de UDX7Config, y volvemos a guardar el proyecto.

La nueva ficha creada con la unit UDX7Config va a ser un diálogo modal, así pues hemos de asegurarnos de tener los siguientes valores en las propiedades de la ficha:

AutoScroll := False
AutoSize := False
BorderIcons := [biSystemMenu]
BorderStyle := bsDialog
BorderWidth := 0
Position := poScreenCenter
Name := FConfDlg

Aunque la propiedad Name no es obligatoria, es conveniente que ponga este nombre pues es que yo he usado y el que verá en los fuentes de este tutorial.

Si usa el Dialog Wizard (asistente de diálogos) para crear el diálogo las propiedades mencionadas tendrán estos valores.

Como cautela final, podemos usar el evento OnCreate del diálogo para asegurarnos de que estos son los valores que tienen dichas propiedades.

Como en el anterior tutorial, vamos a trabajar primero sobre UDX7Config.

Para ahorrarnos teclar de mas copiamos el código hecho en la sección implementation de la unit del tutorial 2 y lo pegamos en la misma sección de nuestra nueva unit; también hemos de copiar y pegar los tipos pCaps y TCaps en la sección interface.

Todavía nos queda meterle mano a la función EnumModesCallBack para que se adapte a nuestras nuevas necesidades, pero eso lo haremos luego; primero hemos de añadir un par de declaciones en la sección interface empezando por una nueva estructura de datos:

TModosV = Record
   mAncho,
   mAlto,
   mColores,
  mCooperacion : DWORD;
end;

Esta estructura nos servirá para guardar la información que necesitamos de los modos de video soportados por el sistema, así como el modo de video elegido.

Lo siguiente es poner los controles que necesitamos en la ficha del diálogo para poder seleccionar el modo de video que mas nos interese, a saber: 1 TLabel, 1 TComboBox, al que llamaremos cbModosV, 2 TRadioButton: rbVentana y rbPantalla, 1 TCheckBox: chbAcelHard, y 2 TButton. El aspecto final de la ficha ha de ser parecido al de la imagen 1

(Imagen 1)

Para terminar con la sección interface declaramos la función que necesitamos para obtener la configuración del sistema:

 function ObtenerConfig(pSysCaps : pCaps; pModosV : pointer;
                                    var modo : integer) : boolean;

que además de recoger las capacidades y los modos de video, devuelve el modo que queremos utilizar en la variable modo, y si hay algún error o se pulsa el botón Cancel devolverá FALSE.

Vayamos a la sección implementation.

Lo primero es modificar EnumModesCallBack para que guarde los modos de video en la nueva estructura de datos que declaramos en la sección interface, así que borramos el código que tiene (si es la copia del ejemplo tutorial 2) y ponemos el siguiente código:

function EnumModesCallBack(const lpDDSurfaceDesc : TDDSurfaceDesc2;
                                                  lpContext : Pointer) : HResult; stdcall;
var
 pModo : Pointer;
begin
 // Reservamos memoria para los datos recogidos
 pModo:=AllocMem(SizeOf(TModosV));
 // Extraemos los datos del modo de pantalla soportado
 With TModosV(pModo^) Do
 begin
  mAncho:=lpDDSurfaceDesc.dwWidth;
  mAlto:=lpDDSurfaceDesc.dwHeight;
  mColores:=lpDDSurfaceDesc.ddpfPixelFormat.dwRGBBitCount;
 end;
 // Guardamos los datos para su uso futuro
 TList(lpContext).Add(pModo);
 // Vamos a por el siguiente modo
 Result := DDENUMRET_OK;
end;

De lo hecho en el tutorial 2 es lo único que vamos a modicar pues CreaDX7, ObtenModosVideo, y Aceleracion3D, nos sirven tal y como están.

Es hora de poner código a la nueva función ObtenerConfig.

 Lo primero que ObtenerConfig hace es detectar el modo de pantalla en el que está el escritorio de windows para usarlo como modo predeterminado de la aplicación DirectX, lo cual hace mediante una llamada a la función API de windows GetDeviceCaps. En realidad hacemos 3 llamadas, una para obtener la resolución horizontal, otra para la resolución vertical y la tercera para obtener la profundidad de color en bits por pixel. La función requiere 2 parámetros, el HDC del escritorio y el correspondiente flag que determina el dato a devolver por la función. Estos flags están ampliamente documentados en la ayuda de Delphi.

Seguidamente, llamamos a CreaDX7, para verificar que el sistema soporta DirectX 7, a ObtenModosVideo, y a Aceleracion3D. Si no hubo problemas, con estas 3 llamadas recogemos la información que necesitamos. Obsévese que una vez obtenidos los datos nos deshacemos de la interfaz temporal creada con CreaDX7 ya que DelphiX crea su propia interfaz y, pese a que pueden coexistir 2 objetos DirectX, la nuestra sólo serviría para quitarle recursos a la aplicación.

Por último se crea el diálogo y se utiliza la información recogida para poner, en cbModosV, los modos de video disponibles, y, en el chbAcelHard, el tipo de aceleración 3D que usará la aplicación. Por si surgiera algún problema imprevisto, hemos puesto a FALSE la variable Result de la función antes de crear el diálogo para asegurarnos de que no se devolverá TRUE, lo cual causaría el fracaso de la aplicación con consecuencia imprevisibles.

Como ya se habrá imaginado el lectos, este diálogo nos sirve para determinar el modo de vídeo que usaremos, si vamos a trabajar en modo ventana o a pantalla completa y si podemos usar aceleración 3D por hardware o no (o si la queremos). Nótese que según seleccionemos el modo ventana o el modo pantalla completa modificamos el campo mCooperacion de TModosV. Este campo podría ser perfectamente un flag booleano (en cuyo caso hubiera usado el identificador mVentana), pero he decidido usar el tipo DWORD para utilizar los correspondientes flags DirectX que determinan si se usa el modo ventana o el modo pantalla completa. En el primer caso el flag es: DDSCL_NORMAL; en el segundo hemos de combinar los flags DDSCL_EXCLUSIVE y DDSCL_FULLSCREEN mediante un Or.

function ObtenerConfig(pSysCaps : pCaps; pModosV : pointer;
                                       var modo : integer) : boolean;
Var
 IDDraw7Temp : IDirectDraw7; // Interfaz temporal para recoger información
 DeskDC : HDC;
 DeskModeStr : String;
 i : integer; // contador para bucles For y otros
begin
 // Obtenemos el modo de pantalla actual del escritorio
 DeskDC:=GetDC(GetDesktopWindow);
 DeskModeStr:=IntToStr(GetDeviceCaps(DeskDC,HORZRES))+'x'+
                          IntToStr(GetDeviceCaps(DeskDC,VERTRES))+' '+
                          IntToStr(GetDeviceCaps(DeskDC,BITSPIXEL))+'bits';
 // Verificamos que el sistema soporta DirectX 7
 Result:= CreaDX7(IDDraw7Temp);
 If Not Result Then
  begin
   IDDRaw7Temp:=nil; // nos aseguramos de no dejar basura
   Exit;
  end;
 // Recogemos los datos sobre modos de video
 Result:=ObtenModosVideo(IDDraw7Temp, pModosV);
 If Not Result Then
  begin
   IDDRaw7Temp:=nil; // nos aseguramos de no dejar basura
   Exit;
  end;
 // Recogemos los datos sobre las capacidades del sistema
 Result:=Aceleracion3D(IDDraw7Temp, pSysCaps);
 If Not Result Then
  begin
   IDDRaw7Temp:=nil; // nos aseguramos de no dejar basura
   Exit;
  end;
 // Ya no necesitamos la interfaz temporal
 IDDRaw7Temp:=nil;
 // Asegura que se devuelve ERROR si algo sale mal
 Result:=False;
 Try
  // Crea e inicializa el diálogo de configuración
  Application.CreateForm(TFConfDlg,FConfDlg);
  FConfDlg.rbVentana.Checked:=True;
  FConfDlg.rbPantalla.Checked:=False;
  // Marca la casilla de aceleración 3D por hardware si hay tarjeta aceleradora
  FConfDlg.chbAcelHard.Checked:=(TDDCaps(pSysCaps.HAL^).dwCaps And DDCAPS_3D)=DDCAPS_3D;
  // Construye la lista de modos de video
  With TList(pModosV) Do
  begin
   Pack; // Nos aseguramos de que no haya nodos libres intercalados
   FConfDlg.cbModosV.Clear; // Nos aseguramos de tener vacío el combobox
   For  i:=0 To count-1 Do
    With TModosV(Items[i]^) Do
     FConfDlg.cbModosV.Items.Add(Format('%dx%d %dbits', [mAncho, mAlto, mColores]));
  end; { Fin With }
  // Seleccionamos el modo del escritorio como preseleccionado
  FConfDlg.cbModosV.ItemIndex:=FConfDlg.cbModosV.Items.IndexOf(DeskModeStr);
  // Activamos el diálogo de configuración
  Result:=FConfDlg.ShowModal=mrOk;
  If Result Then
   begin
    modo:=FConfDlg.cbModosV.ItemIndex;
    With TModosV(TList(pModosV).Items[modo]^) Do
    begin
     If FConfDlg.rbVentana.checked Then
      mCooperacion:=DDSCL_NORMAL
     Else
      mCooperacion:=DDSCL_EXCLUSIVE Or DDSCL_FULLSCREEN;
    end; { Fin With }
   end;
 Finally
  FConfDlg.Free;
 end;
end;

Ya sólo nos queda implementar el evento OnClick de los controles rbVentana y rbPantalla del diálogo (asumo que el lector sabe como vincular los eventos comunes de varios controles a un mismo método):

procedure TFConfDlg.rbVentanaClick(Sender: TObject);
begin
 Case (Sender As TRadioButton).tag Of
  0 : rbPantalla.Checked:=Not rbVentana.Checked;
  1 : rbVentana.Checked:=Not rbPantalla.Checked;
 end; { Fin Case }
end;

Para que este código funcione correctamente es necesario que la propiedad Tag de rbVentana tenga valor 0 y la de rbPantalla sea 1 De nuevo usamos el inspector de objetos para verificarlo, o si queremos estár completamente seguros ponemos el correspondiente código en TFConfDlg.FormCreate.

Con esto acabamos con el código de UDX7Config. Ya estamos en condiciones de desarrollar nuestra aplicación DirectX.

Aunque ya tenemos hecha nuestra ficha DelphiX, aún le faltan cosas que pondremos en la sección private:

lModosV : Tlist;
SysCaps : pCaps;
InfoTxt : TStringList;
iModoV : Integer; // índice del modo de video actual
procedure ActivaModoVideo;
lModosV es la lista de modos de video soportados,
SysCaps ya lo conocemos :))
InfoTxt contendrá el texto que se verá si todo funciona correctamente,
iModoV ¿qué decir de iModoV? 8D
ActivaModoVideo el método que hace lo que su nombre indica.

¿He dicho ya que conviene guardarlo todo por si se va la luz? 8D

Dado que lo primero que hemos de hacer es obtener la información para configurar DelphiX correctamente, llamaremos a ObtenerConfig desde el evento TForm1.FormCreate. Por supuesto, hemos de tener UDX7Config en el uses de la sección interface de UD3DX7Ini3. Pero antes de meternos con Form1.FormCreate, vamos a implementar el método ActivaModoVideo:

procedure TForm1.ActivaModoVideo;
begin
 // Usa el modo pantalla completa si se seleccionó
 With TModosV(lModosV.Items[iModoV]^) Do
 begin
  If Not((mCooperacion And DDSCL_NORMAL)=DDSCL_NORMAL) Then
   DXDraw1.Options:=DXDraw1.Options+[doFullScreen]
  Else
   begin
    // Selecciona modo Ventana (si es soportado) o Pantalla completa
    If ((SysCaps.HAL.dwCaps2 And DDCAPS2_CANRENDERWINDOWED) Or
        (SysCaps.HEL.dwCaps2 And DDCAPS2_CANRENDERWINDOWED))=
        DDCAPS2_CANRENDERWINDOWED
    Then
      DXDraw1.Options:=DXDraw1.Options-[doFullScreen]
     Else
      DXDraw1.Options:=DXDraw1.Options+[doFullScreen]
   end;
 end; { Fin With }
 If doFullScreen In DXDraw1.Options Then
  begin
   // Tamaño de la ventana fijo y sin cursor
   BorderStyle:=bsNone;
   DXDraw1.Cursor:=crNone;
  end
 Else
  begin
    // Ventana de dimensiones variables y cursor visible
   BorderStyle:=bsSizeable;
   DXDraw1.Cursor:=crDefault;
  end;
end;

Como vemos el método comprueba si se ha seleccionado el modo ventana o el modo pantalla completa y actualiza la propiedad Options de DXDraw1. Acto seguido, prepara la ficha en función del modo elegido.

Aunque este código puede ir perfectamente incluído en Form1.FormCreate nos interesa sacarlo a un método distinto pues vamos a llamarlo desde el método que usaremos para alternar entre el modo ventana y pantalla completa. Pero primero hay que incializar correctamente la aplicación:

procedure TForm1.FormCreate(Sender: TObject);
begin
 // Desactiva el cronómetro
 DXTimer1.Enabled:=False;
 // Inicializa la lista de modos de video
 lModosV:=TList.Create;
 // Inicializa el texto informativo
 InfoTxT:=TStringList.Create;
 InfoTxt.Clear;
 InfoTxt.Sorted:=False;
 InfoTxt.Add('Tutorial 3: Inicializando III (3D modo directo)');
 InfoTxt.Add('FPS:');
 InfoTxt.Add('Res:');
 // Reserva memoria para los elementos que lo precisan
 SysCaps:=AllocMem(SizeOf(TCaps));
 SysCaps.HAL:=AllocMem(SizeOf(TDDCAPS));
 SysCaps.HEL:=AllocMem(SizeOf(TDDCAPS));
 // Recoge la información para configurar la aplicación
 If Not ObtenerConfig(SysCaps, lModosV, iModoV) Then
 begin
  MessageDlg('Error al obtener la configuración del sistema.', mtError,
             [mbOk],0);
  Close;
  Exit; //  nos aseguramos de salir del método antes de llegar al end
 end;
 // Ajustamos las opciones de DXDraw1
 // No dejamos que la aplicación bloquee el sistema si se cuelga
 DXDraw1.Options:=DXDraw1.Options+[doAllowReboot];
 // Quitamos el ajuste al area visible
 DXDraw1.Options:=DXDraw1.Options-[doStretch];
 // Quitamos el centrado
 DXDraw1.Options:=DXDraw1.Options-[doCenter];
 // Del intercambio de página nos encargamos nosotros
 DXDraw1.Options:=DXDraw1.Options-[doFlip];
 // 3D
 DXDraw1.Options:=DXDraw1.Options+[do3D];
 // Modo Directo
 DXDraw1.Options:=DXDraw1.Options+[doDirectX7Mode];
 DXDraw1.Options:=DXDraw1.Options-[doRetainedMode];
 // Dejamos que DXDraw seleccione el Driver adecuado
 DXDraw1.Options:=DXDraw1.Options+[doSelectDriver];
 // Aceleración 3D
 If (SysCaps.HAL.dwCaps And DDCAPS_3D)=DDCAPS_3D Then
  begin
   DXDraw1.Options:=DXDraw1.Options+[doHardware];
   InfoTxt.Add('Aceleración por Hardware');
  end
 Else
  begin
   InfoTxt.Add('Emulación por software');
   DXDraw1.Options:=DXDraw1.Options-[doHardware];
  end;
 // Z-Buffer
 If (SysCaps.HAL.dwZBufferBitDepths>0) Or
    (SysCaps.HEL.dwZBufferBitDepths>0)
 Then
  DXDraw1.Options:=DXDraw1.Options+[doZBuffer]
 Else
  DXDraw1.Options:=DXDraw1.Options-[doZBuffer];
 
  // Este parámetro depende de si tenemos o no una tarjeta AGP y si
  // tiene o no suficiente memoria. Por lo general una tarjeta AGP
  // tendrá unos 4 MB de memoria mínimo que serán suficientes para
  // trabajar por lo que no necesitaremos usar memoria RAM del sistema,
  // siempre mucho mas lenta.
  DXDraw1.Options:=DXDraw1.Options-[doSystemMemory];
 // Terminamos la incialización de variables y propiedades.
 ActivaModoVideo;
end;

Si la llamada a ObtenerConfig tiene éxito, se modifican las distintas opciones de DXDraw1.Options y finalmente llamamos a ActivaModoVideo para finalizar la inicialización.

Como en el anterior tutorial, usaremos el evento OnClose para no dejar basura en la memoria del sistema:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 // Liberamos la memoria ocupada por los objetos y estructuras no
 // creados por el compilador
 InfoTxt.Free;
 lModosV.Free;
 Try
  FreeMem(SysCaps.HEL);
  FreeMem(SysCaps.HAL);
  FreeMem(SysCaps);
 Except
 end;
end;

De nuevo, como en el tutorial 1, usamos el evento OnInitialize de DXDraw1 para reactivar el cronómetro, y el evento OnInitializeSurface para incializar el marco de renderizado (Viewport). (Ver ejemplo tutorial 1). Pero para configurar correctamente el marco de renderizado necesitamos modificar las propiedades de visualización de DXDraw1 para lo cual usaremos el evento OnInitialiazing:

procedure TForm1.DXDraw1Initializing(Sender: TObject);
begin
 // Inicializa los paramatros del modo de pantalla
 With TModosV(lModosV.Items[iModoV]^) Do
 begin
  // Fijamos las distintas dimensiones relacionadas con el modo
  // seleccionado
  DXDraw1.Width:=mAncho;
  DXDraw1.Height:=mAlto;
  DXDraw1.Display.Width:=mAncho;
  DXDraw1.Display.Height:=mAlto;
  DXDraw1.Display.BitCount:=mColores;
  DXDraw1.SurfaceWidth:=mAncho;
  DXDraw1.SurfaceHeight:=mAlto;
 end;
end;

Para terminar, ya sólo nos queda hacer unas pequeñas modificaciones en los eventos  OnTimer del cronómetro y en OnKeyDown de la ficha.

En la sección var del primero añadimos una variable tipo integer

i : integer;

que usaremos como contador para escribir nuestro texto, y para esto último, justo tras el comando

DXDraw1.D3DDevice7.EndScene;

pondremos el siguiente código:

 InfoTxt.Strings[1]:='FPS: '+inttostr(DXTimer1.FrameRate);
 With DXDraw1.Display Do
  InfoTxt.Strings[2]:='Modo '+Format('%dx%d %dbpp', [Width, Height, BitCount]);
  // Ponemos un texto en nuestra vista
  with DXDraw1.Surface.Canvas do
  begin
    try
      Brush.Style := bsClear;
      Font.Color := clInfoBk;
      Font.Size := 12;
      For  i:=0 To InfoTxt.Count-1 Do
       Textout(15, 15+16*i, InfoTxt.Strings[i]);
    finally
      Release; {  Indispensable  }
    end;
  end;

Con el evento OnKeyDown vamos, ademas de cerrar la aplicación, a alternar los modos ventana y pantalla completa:

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  {  Condición de salida de la aplicación  }
  if Key=VK_ESCAPE then
    Close;
  { Alterna modo Ventana <-> Pantalla completa }
  if (ssAlt in Shift) and (Key=VK_RETURN) then
  begin
    DXDraw1.Finalize;
    if doFullScreen in DXDraw1.Options then
     begin
      RestoreWindow;
      TModosV(lModosV.Items[iModoV]^).mCooperacion:=DDSCL_NORMAL;
      ActivaModoVideo;
     end
    else
     begin
      StoreWindow;
      TModosV(lModosV.Items[iModoV]^).mCooperacion:=DDSCL_EXCLUSIVE Or
                                                                                              DDSCL_FULLSCREEN;
      ActivaModoVideo;
     end;
    DXDraw1.Initialize;
  end;
end;

Ya tenemos la aplicación lista para compilar y funcionar.


Con esto finalizamos el tutorial 3.

En el siguiente tutorial vamos a tratar de ver algo moviéndose en la pantalla.

¿Qué tal un triángulo de colores?


Descargar el Código Fuente de Ejemplo


 

 
Hosted by www.Geocities.ws

1