|
|
|
Autor |
|
|
Á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. 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
que usaremos como contador para escribir nuestro texto, y para esto último, justo tras el comando
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? |