Cenni su come ho fatto la conversione

Visto che nello ZIP trovate tutti i programmi e le librerie convertiti e che ho aggiunto dei commenti esplicativi nel codice nei punti in cui ho fatto dei cambiamenti, non la farò molto lunga in questa sezione. Se davvero vi interessano le particolarità di DJGPP, allora studiatevi il mio codice, consultate le faq di DJGPP, la documentazione della libreria del C, ecc... altrimenti potete imparare la computergrafica usando DJGPP, il libro di Andrea e le librerie che ho convertito senza preoccuparvi troppo di come ho fatto la conversione. Quindi questa sezione è per "esperti".

Vi avverto che a volte ci sono delle piccole differenze di utilizzo delle librerie... guardate i commenti prima delle definizioni delle funzioni nei file .h, sono spiegate. Ad esempio in alcune versioni di demogfx.h invece di avere una unica procedura put_pixel, che funziona sia su uno schermo virtuale che sullo schermo reale, ne avete due: putpixel per lo schermo reale e put_pixel per quello virtuale. Siccome ho implementato un doppio meccanismo di accesso alla memoria video (spiegazioni in merito tra poco), a volte sono state necessarie queste piccole modifiche all'interfaccia, perchè ho voluto far sì che l'interfaccia delle librerie fosse la stessa sia che attivate un metodo che l'altro.
Se avete particolari problemi, scrivetemi... comunque come vedete si tratta di modifiche di poco conto.

Cominciamo con la conversione della PutPixel, vedi pag. 10 del libro di Andrea. Ci sono un sacco di modi di fare questo in DJGPP (tutti i modi possibili li trovate nei vari file .c della directory cap1, ci sono anche lì commenti e spiegazioni in mezzo al codice).

La differenza sostanziale tra DJGPP e il Watcom è questa: come DJGPP, il Watcom C/C++ crea programmi in modo protetto, ma mentre nel Watcom il primo megabyte di memoria può essere acceduto direttamente dai programmi senza particolari accorgimenti (tranne che la memoria è lineare invece di essere basata su segmenti e offset come nel modo reale - tutto sommato questa è una semplificazione), in DJGPP la cosa è differente. Non potete accedere al segmento del video direttamente senza alcuni trucchetti. Questo perchè quando gli exe creati con DJGPP vengono caricati, di solito vengono allocati oltre il primo megabyte di memoria.

Ci sono essenzialmente due metodi, uno più sicuro e compatibile con tutti i DPMI, l'altro trucchettoso, ma a fino al 9-10% più veloce. Per selezionare quale metodo usare ho aggiunto anche questa "feature" alle librerie: c'è un flag (chiamato DEBUG oppure SECURE_ACCESS) che quando viene posto a 1 è attiva la protezione della memoria (l'accesso alla memoria video avviene tramite un selettore apposito); quando è 0 usa il metodo Fat DS, lo stesso usato dal mitico Quake per guadagnare qualche % in più di velocità. Così durante il debugging dei programmi uno attiva la protezione e se ha un puntatore "selvaggio" ottiene un GPF e l'indicazione del punto dove è avvenuta la violazione d'accesso, invece di un crash; poi quando il programma è finito e corretto basta solo cambiare il flag e viene compilato con codice più efficiente (ho usato semplicemente la compilazione condizionale per ottenere questo).

Come realizzo l'accesso FAT DS? Uso la funzione __djgpp_nearptr_enable() (descritta in libc.inf) per ottenere l'accesso ai primi 640K di memoria. Ho creato una funzione apposta, detta Enable_Access(), che dovete chiamarla all'inizio del programma. Eccola:

void Enable_Access(void)
{
     if (__djgpp_nearptr_enable())
        screen = (unsigned char *)(__djgpp_conventional_base + 0xa0000);
     else
     {
        printf("this program can't run with this DPMI server\n");
        exit(1);
     }
}

Questa funzione fa questo: cambiamo il limite del descrittore di segmento memorizzato in DS a
`0xffffffff' (cioè., -1), usando la funzione di libreria fatta apposta `__djgpp_nearptr_enable'.
Dopo aver fatto questo, abbiamo accesso a tutta la memoria che è correntemente mappata nel descrittore; dobbiamo solo aggiungere il valore della variabile globale __djgpp_conventional_base
all'indirizzo di destinazione, in questo caso l'indirizzo lineare 0xa0000 (che appartiene alla VGA), dopodichè screen è un puntatore pronto per l'accesso diretto.

Alla fine del programma chiamate la funzione Disable_Access() che non è altro che un altro nome per la funzione di libreria (vedi help sulla libc in RHIDE) __djgpp_nearptr_disable();

void Disable_Access(void)
{
       __djgpp_nearptr_disable();
}
 

Praticamente così ritornate al regolare modo protetto.
Per poter utilizzare queste funzioni dovete includere un altro file, così:

#include <sys/nearptr.h>

Un'altra piccola differenza è che dovete includere il file dos.h invece di i86.h.

Così potete accedere ad indirizzi assoluti come se fossero array nello spazio di indirizzamento del vostro programma. Come dicevo prima se volete la massima velocità e non vi preoccupa molto il fatto di perdere la protezione della memoria per un po', questo è il metodo da adottarsi con DJGPP.

L'altro metodo, quello che mantiene la protezione della memoria, usa invece le funzioni _far* della libreria del C di DJGPP (a cui rimando) e viene usato sempre quando DEBUG oppure SECURE_ACCESS è posto ad 1; in pratica quindi noterete che ci sono due versioni di ciascuna primitiva grafica: una viene compilata quando DEBUG=0 e usa l'accesso diretto, l'altra viene compilata con DEBUG=1 e usa l'accesso indiriretto con _far*. Ripeto ancora una volta di usare DEBUG=1 durante il debugging dei programmi e DEBUG=0 (o SECURE_ACCESS nel file demovbe2.h) solo per la versione finale del programma, quando siete sicuri della sua correttezza.
Infatti se avete una routine grafica fatta male che scrive fuori della memoria video per sbaglio,
se DEBUG=0 potete far andare in crash il computer, mentre questo non può succedere quando è attiva la protezione della memoria (DEBUG=1) e inoltre in questo caso DJGPP vi fornisce una completa diagnostica dell'errore. Un'altra cosa consigliabile per facilitare il debugging è di disattivare tutte le ottimizzazioni durante il debugging (infatti il trace non funziona bene con le ottimizzazioni attivate!)

Anche nel modo-x l'accesso diretto alla memoria  va realizzato esattamente come ho appena descritto.  A proposito nella mia homepage (vedi sezione varie) trovate pure un mio articolo scaricabile sul modo-x.

nota sul codice allegato:
Ho sempre cercato di fare del mio meglio per scrivere codice efficiente: ad es. provate a compilare con -O3 e ad eseguire il campo stellato con 10000 stelle (cap2/esempio3.exe); va bene come come velocità? lo sfarfallio è assente; se riuscite a fare di meglio, mandatemi il programma, così imparo pure io. Per quanto riguarda l'assembly inline, è difficilissimo battere il compilatore DJGPP quando è attivato il massimo livello di ottimizzazione di velocità, cioè -O3 (almeno io non sono in grado di farlo!): ho provato a convertire pari pari il codice assembly inline che usa Andrea nella strana sintassi di DJGPP, ma, misurando i tempi di esecuzione, ho visto che il guadagno era insignificante e potete verificare anche voi questo fatto. Date un'occhiata a cap8/demo640.h e cerca con un text editor la parola "asm". Perciò ho usato sempre codice C.

Comunque se volete imparare a scrivere assembly inline per DJGPP (ma ripeto che nel caso di questi programmi molto probabilmente dovete essere dei geni dell'assembly per battere il compilatore con l'opzione -O3 attivata), consultate le faq; vi allego offline anche una lettera che spiega come interfacciare codice assembly inline con codice C in maniera sicura.

Tenete conto che DJGPP non accetta la sintassi Intel dell'assembly (quella descritta nel libro), ma un' altra sintassi per lo stesso linguaggio, quella della AT&T. Consultate le FAQ, vedi sezione 17.2 Converting between Intel ASM syntax and AT&T syntax o questi miei appunti che ne sono la traduzione; esistono anche dei convertitori automatici da una sintassi all'altra che vi possono essere utili (vedi al solito le faq).
 

La conversione di demovb2.h è stata la più dura: mi sono basato su frammenti di codice trovati nelle mailing list e su un'altra piccola libreria per DJGPP (vedi file vesa.zip nello ZIP, che ho scaricato qui:

http://www.geocities.com/ResearchTriangle/5603/famebuffer_example.html )

Dentro questo zip c'è anche un file vesa.txt con tutte le specifiche del VBE 2.0, utile, perciò l'ho allegato.

Brevemente, eccovi una descrizione delle due più importanti e meno ovvie modifiche da apportare (altre modifiche sono state necessarie, guardatevi il codice di demovbe2.h)

1) Occore aggiungere la direttiva `__attribute__((packed))' dopo la definizione di ogni campo degli struct VbeInfoBlock e ModeInfoBlock (vedi libro, cap7, non sto qui a riscrivere tutti gli struct); questa serve per impedire a GCC di inserire dei gaps tra alcuni membri di uno struct per allinearli opportunamente alla scopo di velocizzarne l'accesso. La direttiva è l'unico modo per garantire che questi struct abbiano la sizeof giuste (rispettivamente di 512 e 256 bytes).

2) Si usa il metodo Fat DS (lo stesso usato per accedere alla memoria video in 0xA0000 descritto prima), con l'importante differenza che prima bisogna mappare il linera frame buffer nella memoria fisica con la funzione apposita di DJGPP __dpmi_physical_address_mapping (vedi help di libc, mio codice, faq di djgpp).
 
 
torna al menu prossima sezione: Come chiedere aiuto