DJGPP V2 QuickGraphics Programming Guide

 

Indirizzo e-mail dell'autore: avly@castle.net
HomePage dell'autore: http://www.castle.net/~avly/djgpp.html
Traduzione: MrCODE (mailto:mrcode@tiscalinet.it)
HomePage Traduttore: http://web.tiscalinet.it/mrcode

Questo è un piccolo tutorial per permettere a chiunque di fare programmazione grafica usando DJGPP Version 2, l'eccellente porting DOS dello GNU C/C++. Assumerò che il lettore abbia già qualche conoscenza della programmazione grafica sotto DOS, magari anche se usando differenti compilatori e modelli di memoria (n.d.t. scordatevi il tiny,small,medium,huge del BorlandC++, qui si viaggia in modalità protetta, tutta la memoria è sotto i nostri piedi e può essere indirizzata LINEARMENTE!!). Saranno discussi i modi video LINEARI (VGA mode 13h e VBE 1.2/2.0, non sarà trattata la modalità protetta).Come consiglio generale, sarebbe d'uopo farsi una bella "letturina" delle seguenti faq: , faq102.zip e faq211b.zip che trovate con la distribuzione del DJGPP o nel sito principale.
Si può anche iscriversi al newsgroup di competenza comp.os.msdos.djgpp o ottenere ulteriori info's visitando il sito ufficiale del DJGPP Delorie Software. Si possono anche ripescare le vecchie mail sul DJGPP al seguente indirizzo: mail archives ,in alcuni casi possono rivelarsi davvero utili!!
Una buona fonte di informazioni viene anche dalla consultazione degli stessi file header del DJGPP, in particolar modo vi consiglio uno studio approfondito dei seguenti: go32.h, dpmi.h, pc.h, sys/farptr.h, sys/nearptr.h e sys/movedata.h. 

VGA Mode 13h

In modalità REALE per scrivere i pixel nella memoria video si inizia dalla locazione $A000:0000. I programmi scritti con il DJGPP V2 non possono accedere a tale zona di memoria, con un puntatore "near", così c'è bisogno di impostare un selettore per accedervi. Fortunatamente abbiamo già pronto tale selettore, è definito come _dos_ds nel file include go32.h . Impostando un indirizzo real mode del tipo segmento:offset (qualsiasi indirizzo di memoria DOS) e _dos_ds, possiamo accedere allo stesso indirizzo anche se siamo in modalità protetta!
Come avviene esattamente? In assembly a basso livello, carichiamo il segment register con il valore del selettore, e dopo copiamo tale valore (segment*16+offset), dentro un'altro registro. A questo punto possiamo usare la coppia segmento:registro come puntatore. Ecco come:


short our_global_selector;
...
our_global_selector = _dos_ds;
...
movw _our_global_selector, %es
movl $0xa0000, %edi ;** 0xA000*16 + 0x0000 = 0xA0000


Nota: ricordatevi che stiamo usando la sintassi dell'assembler AT&T 
Adesso possiamo usare es:edi per scrivere nella memoria video. Ecco una routine putpixel pronta all'uso: 

movw _our_global_selector, %es
movl $0xA0000, %edi
movw _y, %ax
imulw $320, %ax
addw _x, %ax
addw %ax, %di
movb _color, %al
stosb

Si possono anche usare altre coppie di registri, a seconda di come vi torna più utile: 
movw _our_global_selector, %fs
movl $0xA0000, %ebx
...
movb _color, %al
movb %al, %fs:(%ebx)

Ci sono molte funzioni di libreria che possono essere usate per scrivere nella memoria video, cominciamo con il trucco del farptr: 
#include <go32.h>
#include <dpmi.h>
#include <sys/farptr.h>

#define putpixel(x,y,c) _farpokeb(_dos_ds, 0xA0000 + (y)*320 + (x), (c))

Con l'ottimizzazione inserita, il DJGPP inserirà inline la chiamata a _farpokeb.
Passando il selettore ad ogni chiamata a _farp* rallenteremo ogni cosa! Possiamo aggirare così l'ostacolo:
/* circle routine */
...
_farsetsel(_dos_ds)

/* loop */
...
_farnspokeb(0xA0000 + y*320 + x, color);
...
/* end loop */

_farsetsel precaricherà il registro fs con _dos_ds per le successive chiamate a _farns* . Questo registro di segmento non è detto che contenga il selettore eccetto immediatamente dopo la chiamata a _farsetsel ! 

Esaminiamo il trucco del nearptr . Naturalmente queste funzioni spengono la protezione della memoria. Direttamente dal file sys/nearptr.h: NO WARRANTY: WARNING, since these functions disable memory protection, they MAY DESTROY EVERYTHING ON YOUR COMPUTER! 
#include <go32.h>
#include <dpmi.h>
#include <sys/nearptr.h>

unsigned char *videoptr = (unsigned char *)0xA0000;

__djgpp_nearptr_enable(); 
videoptr[y*320 + x + __djgpp_conventional_base] = color;
__djgpp_nearptr_disable();

Facile! Basta ricordarsi che __djgpp_conventional_base non è costante. Cambia attraverso le chiamate a funzioni di allocazione di memoria.

C'e' ancora un'altra maniera per accedere alla memoria video:
#include <go32.h>
#include <dpmi.h>
#include <sys/movedata.h>

unsigned char *videoptr = (unsigned char *)0xA0000;
unsigned char *doublebuffer = (unsigned char *)malloc(320*200);

void copy_buffer(void) 

dosmemput(doublebuffer, 320*200, videoptr);
}

void copy_buffer2(void) 
{
movedata(_my_ds(), doublebuffer, _dos_ds, videoptr, 320*200); 
}

Queste funzioni sonoi ovvie. Naturalmente _my_ds() ritorna il nostro selettore del codice; non confondetelo con il _my_ds. Si possono trovare maggiori informazioni nella documentazione e nel file sys/movedata.h

Altre cose che possono essere utili:
#include <go32.h>
#include <dpmi.h>
#include <pc.h>

void setmode(short mode) 
{
__dpmi_regs r;
r.x.ax = mode;
__dpmi_int(0x10,&r);
}

struct rgbstruct 

char red, green, blue; 
};

void setpalette(char color, struct rgbstruct rgb) 
{
outportb(0x3c8, color);
outportb(0x3c9, rgb.red);
outportb(0x3c9, rgb.green);
outportb(0x3c9, rgb.blue);
}


VBE 2.0


Questo non vuole essere un trattato sullo standard VESA. Procuratevi la documentazione SVGAKIT e VBE 2.0 dal sito Scitech per maggiori dettagli e riferimenti. Adesso iniziamo con la struttura dei dati...
#define PACKED __attribute__ ((packed))

#pragma pack(1)
struct VBE_vInfo 
{
char VBESig[4] PACKED;
short VBEVer PACKED;
...
};

struct VBE_mInfo 
{
short ModeAttrib PACKED;
char WinAAttrib PACKED;
... 
unsigned int PhysBasePtr PACKED;
...
};
#pragma pack()

La sola sorpresa sicuramente proverrà dall'uso di PACKED. Questa direttiva istruisce il compilatore ad allineare le variabili al byte e i campi al bit.

Okay, scriviamo una funzione di detecting del VBE:
#include <go32.h>
#include <dpmi.h>
#include <sys/movedata.h>

int VBE_detect(struct VBE_vInfo *vbeinfo) 
{
__dpmi_regs r;

assert(sizeof(*vbeinfo) < _go32_info_block.size_of_transfer_buffer);
strncpy(vbeinfo->VBESig, "VBE2", 4);
r.x.ax = 0x4F00;
r.x.di = __tb & 0x0F;
r.x.es = (__tb >> 4) & 0xFFFF;
dosmemput(vbeinfo, sizeof(*vbeinfo), __tb);
__dpmi_int(0x10, &r);
dosmemget(__tb, sizeof(*vbeinfo), vbeinfo);
...
}

Cosa abbiamo combinato? Sappiamo che la funzione di detecting 0x4F00 necessita di un buffer in memoria che risieda nello spazio di memoria DOS sotto il primo MegaByte, dove ritornerà la tabella dei modi video, le stringhe OEM, etc. Così necessitiamo di allocare spazio sotto 1MB, per una quantità equivalente a sizeof(struct VBE_vInfo).Possiamo usare per questo __dpmi_allocate_dos_memory(), ma c'è un metodo migliore. DJGPP usa internamente un global transfer buffer , solitamente largo 4KB, e noi possiamo usarlo come il nostro buffer di memoria convenzionale! Dopo la chiamata, possiamo semplicemente copiare da questo buffer nella nostra variabile, facile non è vero? Questo transfer buffer è definito come _go32_info_block o __tb nel file go32.h. Quindi __tb & 0x0F è semplicemente l' offset della parte reale, e (__tb >> 4) & 0xFFFF è il segmento. 

Adesso una funzione per ottenere informazioni sui modi video VBE_getModeInfo: 
void VBE_getModeInfo(unsigned short mode, struct VBE_mInfo *modeinfo) 
{
__dpmi_regs r;

assert(sizeof(*modeinfo) < _go32_info_block.size_of_transfer_buffer);
r.x.ax = 0x4F01;
r.x.cx = mode;
r.x.di = __tb & 0x0F;
r.x.es = (__tb >> 4) & 0xFFFF;
__dpmi_int(0x10, &r);
dosmemget(__tb, sizeof(*modeinfo), modeinfo);
...
}

Adesso "grabbiamo" l'indirizzo di memoria video lineare per la modalità 640x480x8! 
struct VBE_mInfo modeinfo;
__dpmi_meminfo mi;
unsigned int linear_address;

VBE_getModeInfo(0x101, &modeinfo);
mi.size = (unsigned long)(modeinfo.XRes*modeinfo.YRes);
mi.address = modeinfo.PhysBasePtr;
__dpmi_physical_address_mapping(&mi);
linear_address = mi.address;

Fatto! Usando __dpmi_physical_address_mapping(), siamo capaci di convertire l'indirizzo fisico del device nell'indirizzo lineare che possiamo usare per scrivere in memoria video, proprio come 0xA0000 con il Mode 13h! Ovviamente, prima di iniziare a scrivere nella memoria video, dobbiamo abilitare il modo video: 
r.x.ax = 0x4F02;
r.x.bx = 0x4101;
__dpmi_int(0x10, &r);

E qui abbiamo un trucchetto per usare un nearptr per scrivere pixel! 
unsigned char *videoptr = (unsigned char *)linear_address;

__djgpp_nearptr_enable();
videoptr[y*width + x +__djgpp_conventional_base] = color;
__djgpp_nearptr_disable();


L'accesso con Farptr è un poco più complicato rispetto alla versione del Mode 13h. Qui non abbiamo un selettore per accedere all'indirizzo di memoria lineare della SVGA. Allora come facciamo? Semplice, ce ne costruiamo uno! In più settiamo l'indirizzo base del nostro nuovo selettore al valore del linear_address ,in questo modo ogni scrittura partirà dall' offset 0.
unsigned char *videoptr = (unsigned char *)0x0;
short our_global_selector = __dpmi_allocate_ldt_descriptors(1);
__dpmi_set_segment_base_address(our_global_selector, linear_address);

_farpokeb(our_global_selector, videoptr + y*width +x, color);


La nostra buona funzione movedata è sempre disponibile:
void copy_buffer2(void) 
{
movedata(_my_ds(), doublebuffer, our_global_selector, videoptr, width*height);
}


Un'ultima cosa riguardo l'interfaccia in modalità protetta del VBE. Prima la struttura dati rilevante, come descritto nel SVGAKIT.
#pragma pack(1) 
struct VBE_PMInterface 
{
short pfsetWindow PACKED;
short pfsetDisplayStart PACKED;
short pfsetPalette PACKED;
...
};
#pragma pack()

Questa struttura contiene puntatori ai servizi VBE, se qualcuno volesse accedervi direttamente dalla modalità protetta.
Adesso scriviamo una funzione che recupera tali puntatori a funzione:
int VBE_getPMInterface(struct VBE_PMInterface *vbepmi) 
{
__dpmi_regs r;

r.x.ax = 0x4F0A;
r.x.bx = 0x0000;
__dpmi_int(0x10,&r);
vbedpmi = (struct VBE_PMInterface *)malloc(sizeof(char)*r.x.cx);
dosmemget(r.x.es*16 + r.x.di,sizeof(*vbepmi),vbepmi);
}

Bisogna allocare un buffer di dimensioni r.x.cx bytes dopo la chiamata a 0x4F0A , poi copiare le informazioni dell'interfaccia in modo protetto dalla memoria DOS al buffer. Adesso i puntatori possono essere facilmente recuperati: 
vbepmi + vbepmi->pfsetWindow
vbepmi + vbepmi->pfsetDisplayStart
vbepmi + vbepmi->pfsetPalette

Ovviamente, non dimenticate di copiare l'interfaccia dalla memoria DOS dopo ogni mode set, e liberate il buffer il buffer quando fate lo shutdown del sistema grafico che avete messo in piedi!

VBE 1.2


Non c'è molto da parlare qui, veramente. C'è solo da far vedere la tecnica per il bank switching, visto che non è possibile usare il comodissimo linear frame buffer di cui abbiamo parlato precedentemente:
void bankswitch(short bank) 
{
__dpmi_regs r;
r.x.ax = 0x4F05;
r.x.bx = 0x0000;
r.x.dx = bank;
__dpmi_int(0x10, &r);

/* In AT&T asm:
__asm__ __volatile__("
movw $0x4F05, %%ax;
xorw %%bx, %%bx;
int $0x10"
: : "d" (bank) : "ax", "bx", "dx"
); 
*/
}
Nel modo VBE 101h (640x480x8), si può avere ogni banco di 64K (65536) bytes. Così il banco è calcolato così: 
short bank = (short)((640*y + x) >> 16);
Per copiare il nostro double buffer nella memoria video usando le funzioni nearptr, facciamo:
void copy_buffer(void) 
{
char *source, *dest;

source = doublebuffer;
dest = videoptr + __djgpp_conventional_base;

__djgpp_nearptr_enable();

/* 640*480*8bpp = 307200 bytes = 4*64K + 45056 bytes */

bankswitch(0);
memcpy(dest, source, 65536L);
bankswitch(1<<WinGran);
source += 65536L;
memcpy(dest, source, 65536L);
bankswitch(2<<WinGran);
source += 65536L;
memcpy(dest, source, 65536L);
bankswitch(3<<WinGran);
source += 65536L;
memcpy(dest, source, 65536L);
bankswitch(4<<WinGran);
source += 65536L;
memcpy(dest, source, 45056L);

__djgpp_nearptr_disable();
}
WinGran is just a 16-bit value obtained by: 
VBE_getModeInfo(0x101, &modeinfo);
WinGran=0;
while ((unsigned)(64>>WinGran) != modeinfo.WinGranularity)
WinGran++;


Ecco tutto, oplà!


Altre risorse
Esistono delle eccellenti librerie grafiche e per giochi sviluppate interamente in DJGPP. Alcune di queste sono GRX, Allegro, XLIB per DJGPP, e JLIB. Sono disponibili all'indirizzo x2ftp.oulu.fi e nella directory v2apps/ al sito ufficiale DJGPP.

Standard Disclaimer: All trademarks mentioned are owned by their respective companies. There are absolutely no guarantees, expressed or implied, on anything that you find in this document. I cannot be held responsible for anything that results from the use or misuse of this document. 
Torna alla pagina principale

Torna alla main page! Torna alla pagina principale