[ Home | Capitolo1 | Capitolo 2 | Capitolo 3 | Capitolo 4 | Capitolo 5 ]

Guida alla Programmazione di Giochi con le DirectX di David Joffe

Capitolo 5: Nuovi argomenti vari non scelti

5.0 Su Conv3ds.exe

conv3ds -X: Salva i templates. Niente di cui preoccuparsi, fa parte della natura dei files .x. Utile, a patto che vediate cosa significano i vari parametri (come materials).

conv3ds -m: Unisce tutti gli oggetti in un singolo reticolato (mesh). Utile se non vi servono reticolati separati, e può velocizzare il caricamento.

conv3ds -x: Salva come un file di testo: Certamente non una cattiva idea, specialmente durante il debugging. Aiuta ad avere un controllo rapido sui files, etc.

conv3ds -T: Crea un frame top-level con tutti gli oggetti come frames discendenti. Questo è utile poiché pFrame->Load carica solo il primo frame che trova nel file.

conv3ds -v : Modalità descrittiva (Verbose).

Bug possibili (possibili non in v5, solo in v3) .. sembra avere bisogno che XForms venga resettato in 3dstudio oppure accadono cose strane. Forse lo controllerò..

Quando si usa un oggetto pFrame per caricare un reticolato da un file mesh, e avete bisogno di contenere (ne parlerò di più)

A breve:

Progetto per imparare OpenGL: Confronti etc, per quanto possa fare appello alla mia imparzialità.

Enumerating device drivers

Callbacks

RM contro IM

Un esempio decente di codice D/L-abile.

Commenti agli esempi MS

Come compilare con NMAKE

faq: main: linker error

Matematica per il 3D

Fog + qualche altra tecnica di ottimizzazione del frame rate

Caricare e mostrare un .bmp (o .png :)

Execute buffers contro DrawPrimitive

5.1 Disegno di un pixel con DirectDraw

Microsoft non ha fornito una funzione del tipo "PutPixel" con DirectDraw per disegnare un pixel su una superficie DirectDraw. Certo, nessun gioco disegnerebbe la sua grafica ricorrendo ad una routine PutPixel, dato che sarebbe troppo lento - ma sarebbe stato carino averla come "funzione di utilità", e sarebbe stata anche comoda in materia di testing. Alcune applicazioni che non hanno necessariamente bisogno di velocità, potrebbero trarre vantaggio da una routine come PutPixel. Però, le DirectX non sono state progettate con la comodità in mente.

Ad ogni modo, esistono parecchi modi per scrivere una routine PutPixel. Un modo potrebbe essere di usare funzioni GDI sulla superficie, utilizzando GetDC(). Un altro modo è di usare la funzione IDirectDrawSurface::Blt(), specificando una gestione di un colore solido con un rettangolo 1x1. Un terzo modo è di ottenere un puntatore alla memoria che rappresenta la superficie chiamando IDirectDrawSurface::Lock(), e disegnando il pixel in memoria da soli. Ognuno di questi metodi mostra tecniche generali la cui utilità supera una semplice funzione PutPixel.

5.1.1 PutPixel tramite surface locking

La base di questa tecnica è di ottenere un puntatore alla memoria che rappresenta la superficie. Potete quindi calcolare l'offset in questa memoria per le coordinate del pixel che volete disegnare, e quindi scrivere i bytes che rappresentano il colore del pixel che volete in quella posizione della memoria. Non c'è niente di difficile o complesso, è solo un sacco di sporco lavoro e di salti di buche.

Per default, potreste non avere la possibilità di scrivere direttamente nella memoria di una superficie. Dovrete prima ottenere l'accesso esclusivo a questa superficie, effettuandovi un "locking". Questo viene fatto con una chiamata a Lock().

HRESULT IDirectDrawSurface::Lock( LPRECT pRect, LPDDSURFACEDESC pDDSD, DWORD dwFlags, HANDLE hEvent );
pRect
Area della superficie da lockare. NULL locka l'intera superficie.
pDDSD
Punta allo struct che descrive la superficie.
dwFlags
Flags di controllo.
hEvent
Inutilizzato. Microsoft ama questi parametri inutilizzati.

Il puntatore alla memoria della superficie è uno dei membri della struttura DDSURFACEDESC, e viene riempito dalla chiamata a Lock() con un puntatore valido. Potete quindi scrivere in questa memoria, e quando avrete finito, dovrete solo chiamare Unlock su quella superficie.

Ora, per disegnare un pixel, avete bisogno di capire come i pixel sono rappresentati in memoria, cosa che dipenderà dalla profondità di colore dello schermo in cui si trova la vostra superficie (vedere il Capitolo 2). Ovviamente, se create una routine PutPixel "generica", questa lavorerà senza badare alla modalità dello schermo, dovrete probabilmente specificare il colore in un qualche formato leggibile dall'uomo, come un RGB triplo. La routine PutPixel deve quindi convertirlo, memorizzarlo da qualche parte, e scaricarlo nella memoria della superficie. Io uso un unsigned int per memorizzare tremporaneamente il valore del pixel. Per convenienza, ho creato una funzione CreateRGB che può generare questo unsigned int dai valori RGB forniti con estensione da 0 a 255:


/*--------------------------------------------------------------------------*/
// Create color from RGB triple
unsigned int CreateRGB( int r, int g, int b )
{
   unsigned int pixel;
   switch (g_iBpp)
   {
   case 8:
      // Here you should do a palette lookup to find the closest match.
      // I'm not going to bother with that. Many modern games no
      // longer support 256-color modes.
      break;
   case 16:
      // Break down r,g,b into 5-6-5 format.
      pixel = ((r/8)<<11) | ((g/4)<<5) | (b/8);
      break;
   case 24:
   case 32:
      pixel = (r<<16) | (g<<8) | (b);
      break;
   default:
      pixel =0;
   }
   return pixel;
}
/*--------------------------------------------------------------------------*/

Qui c'è la routine PutPixel attuale, che prende come input una superficie DirectDraw, una locazione (x,y) e un RGB triplo con r,g e b nel raggio di 0 a 255. La funzione restituisce 0 in caso di successo. Notate che la funzione, come CreateRGB, assume l'esistenza di g_iBpp, che spiegherò tra un po'.


/*--------------------------------------------------------------------------*/
int PutPixel( LPDIRECTDRAWSURFACE pDDS, int x, int y, int r, int g, int b )
{
   DDSURFACEDESC   ddsd;       // Surface description
   unsigned int    pixel;      // Store pixel to be dumped to screen
   int             offset;     // Store offset of destination memory location
   unsigned char * szSurface;  // Store pointer to surface
   HRESULT         hr;
   int             pixelwidth; // The width of a single pixel, in bytes

   // Initialize struct information
   ddsd.dwSize = sizeof(ddsd);

   // Calculate how the pixel is represented in memory
   pixel = CreateRGB( r, g, b );

   // Lock entire surface, wait if it is busy, return surface memory pointer
   hr = pDDS->Lock( NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );
   if (FAILED(hr))
      return -1;

   // Get surface memory pointer
   szSurface = (unsigned char*)ddsd.lpSurface;

   // Calculate the width of a pixel in bytes - this is how many bytes
   // we must write to the surface
   switch (g_iBpp)
   {
   case 16: pixelwidth = 2; break;
   case 24: pixelwidth = 3; break;
   case 32: pixelwidth = 4; break;
   default: pixelwidth = 1;
   }

   // Calculate memory offset
   // (Notice I use dwPitch instead of dwWidth - see Chapter 2)
   offset = (y*ddsd.lPitch + x*pixelwidth);

   // Copy pixel onto the surface, need string.h for this
   memcpy( szSurface + offset, &pixel, pixelwidth );

   // Unlock the surface. The parameter may be NULL if you locked the entire
   // surface, otherwise it must be a pointer to the DirectDraw surface memory.
   hr = pDDS->Unlock( NULL );
   if (FAILED(hr))
      return -2;

   return 0;
}
/*--------------------------------------------------------------------------*/

Esistono alcune cose che vale la pena di menzionare su questa funzione. Primo, può essere ottimizzata molto, lo so. Ma nella produzione di un gioco, questa non è la funzione che utilizzerete per tutte le vostre routines di disegno, in ogni modo (Una delle regole dell'ottimizzazione - mai ottimizzare quello che non vi serve). Secondo, crea alcune grossolane premesse sulla vostra architettura - potrebbe non funzionare se il vostro sistema è big endian. Comunque, l'Intel x86 è little endian, e per quello che ne so, le DirectX sono disponibili solo per Intel x86. Terzo, funziona solo su un numero limitato di modalità dello schermo, e un giorno o l'altro, quando la gente comincerà ad utilizzare modalità video con profondità bit più elevate (come 48), il codice non funzionerà più (comunque, per quello che posso dire, le stesse DirectDraw non sono in grado di utilizzare profondità di colore di 48-bit). Comunque, il punto è che, per rendere "corretta" la routine PutPixel qui sopra, c'è bisogno di un po' di lavoro ulteriore.

Il g_iBpp nel codice qui in alto è un integer che rappresenta i bits per pixel della modalità dello schermo della superficie. Potete ottenerlo da DDSURFACEDESC.

Un'altra cosa che vale la pena menzionare è che, se questa superficie DirectDraw è la superficie primaria - cioè, rappresenta l'intero schermo, e siete in modalità windowed e non in modalità full-screen - allora gli offsets del pixel qui sono relativi alla parte in alto a sinistra dello schermo, e non alla finestra della vostra applicazione. Lo troverete da soli disegnando su tutto lo schermo, fino ad utilizzare l'offset della vostra finestra nei vostri calcoli (es. utilizzando la chiamata Win32 ClientToScreen o qualcos'altro).

5.1.2 PutPixel tramite l'utilizzo di IDirectDrawSurface::Blt

Questa routine è provata nell'esempio DDSamp fornito.

Questo metodo utilizza anche la funzione CreateRGB descritta in 5.1.1, quindi andatevela a leggere se non lo avete ancora fatto.

Questo metodo è molto semplice, e probabilmente un po' più "sicuro" del metodo descritto in 5.1.1.


/*--------------------------------------------------------------------------*/
// PutPixel routine for a DirectDraw surface
void DDPutPixel( LPDIRECTDRAWSURFACE pDDS, int x, int y, int r, int g, int b )
{
   HRESULT hr;
   DDBLTFX ddbfx;
   RECT    rcDest;
   POINT   p;
   
   // Safety net
   if (g_pDDS == NULL)
      return;
   
   // Initialize the DDBLTFX structure with the pixel color
   ddbfx.dwSize = sizeof( ddbfx );
   ddbfx.dwFillColor = (DWORD)CreateRGB( r, g, b );
   
   // Prepare the destination rectangle as a 1x1 (1 pixel) rectangle
   p.x = x;
   p.y = y;
   ClientToScreen( g_hWnd, &p );
   OffsetRect( &rcDest, p.x, p.y );
   SetRect( &rcDest, p.x, p.y, p.x+1, p.y+1 );
   
   // Blit 1x1 rectangle using solid color op
   hr = g_pDDS->Blt( &rcDest, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &ddbfx );
   if (FAILED(hr))
      OutputDebugString( (LPCTSTR)"Royal fuckup\n" );
}
/*--------------------------------------------------------------------------*/

Quello che ho fatto qui è:

Questo è quanto.

5.2 Alcune parole sul surface locking

Prima che corriate a fare il locking di ogni superficie che incontrate, è bene menzionare alcune limitazioni che esistono nel surface locking (che cosa vi aspettavate?). Queste vengono riepilogate dai files di help delle DirectX.

Notate che potete avere rettangoli multipli in contemporanea su una superficie locked, a condizione che i rettangoli non si sovrappongano (overlap).

La documentazione DirectX fa anche i seguenti avvertimenti che non ho ben capito:

Copy aligned to display memory. Windows 95 uses a page fault handler, Vflatd.386, to implement a virtual flat-frame buffer for display cards with bank-switched memory. The handler allows these display devices to present a linear frame buffer to DirectDraw. Copying unaligned to display memory can cause the system to suspend operations if the copy spans memory banks.


dj5.html; creato il: 19 Giugno 1998. Aggiornato il 21 Aprile 1999.
Copyright (C) David Joffe 1998,1999.
http://www.geocities.com/SoHo/Lofts/2018/

 

---
Traduzione: Papero - IPG 1999
Eng?Ita! Team
http://www.ItaProGaming.com