                  DJGPP V2 QuickGraphics Programming Guide

This is just a short tutorial to get one started on graphics programming
using DJGPP Version 2, the excellent DOS port of GNU C/C++. I assume one
has some knowledge of programming graphics under DOS, but maybe using a
different compiler or memory model. Linear video modes such as VGA mode 13h
and VBE 2.0 will be covered, as well as some VBE 1.2. I will not discuss
protected mode as I know little of it.

I highly suggest reading the FAQ lists first, faq102.zip and faq202b.zip,
and the online docs, inside the txi*.zip package. There is also a
newsgroup, comp.os.msdos.djgpp. The main site is located at DJGPP Homepage,
where the most up-to-date information is available on DJGPP and where the
mail archives are kept. I find many helpful articles in the mail archives.

Additionally, looking through the header files of DJGPP might help. The
more relevant files are go32.h, dpmi.h, pc.h, sys/farptr.h, sys/nearptr.h
and sys/movedata.h.
---------------------------------------------------------------------------

VGA Mode 13h

In real mode, one writes to the video memory starting at address $A000:0000
to draw graphics. However, DJGPP V2 programs cannot access this address
near by default so one needs a selector spanning it. Luckily, we already
have this selector. It is defined as "_dos_ds" in go32.h. Given a real mode
segment:offset address (any DOS memory address) and _dos_ds, we could
access the same address under protected mode!

How exactly? In low level assembly, one would load a segment register with
the selector's value and then copy the value, "segment*16+offset", into
another register. Then just use this segment:register pair as a pointer. I
will show how simple it is:

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

Note: We are using AT&T syntax for assembly.

Now we can use "es:edi" to write to video memory. How about a putpixel
routine?

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

It is really straightforward once you get the hang of it. You could also
use other register pairs:

        movw _our_global_selector, %fs
        movl $0xA0000, %ebx
        ...
        movb _color, %al
        movb %al, %fs:(%ebx)

---------------------------------------------------------------------------
It even gets easier. There are many library functions you could use to
write to the video memory. Let us start with the farptr hacks.

        #include <go32.h>
        #include <dpmi.h>
        #include <sys/farptr.h>

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

With optimizations turned on, DJGPP will inline the "_farpokeb" call.

Passing the selector to every "_farp*" call can be slow and cumbersome.
However, we can easily take care of this:

        /* circle routine */
        ...
        _farsetsel(_dos_ds)

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

"_farsetsel" just preloads register "fs" with "_dos_ds" for succeeding
"_farns*" calls.  [GOTCHA!] ] This segment register is NOT guaranteed to
contain the selector except immediately after the "_farsetsel" call!
---------------------------------------------------------------------------
Let's examine the nearptr hacks. By the way, these functions turn off all
memory protection.  [GOTCHA!] ] Taken directly from the sys/nearptr.h file,
"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();

Easy!  [GOTCHA!] ] Just remember that "__djgpp_conventional_base" is NOT
constant. It changes across memory allocation calls.
---------------------------------------------------------------------------
There is yet another way to access video memory!

        #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, sizeof(*doublebuffer));
        }

These function calls are fairly obvious. By the way, "_my_ds()" just
returns our code's selector;  [GOTCHA!] ] do NOT confuse it with "_my_ds".
One can find out more about these in the documentation and sys/movedata.h
file.
---------------------------------------------------------------------------
Other stuff that one might find helpful:

        #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

Again, I am not teaching VBE here. Get the SVGAKIT and VBE 2.0 docs from
Scitech for reference. Now, let us start with the data structures:

        #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()

 [GOTCHA!] ] The only surprising thing is probably the "PACKED" part. It
just tells the compiler to align variables to bytes and fields to bits.
---------------------------------------------------------------------------
Okay, let's write a VBE detect function.

        #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);
          ...
        }

What did we just do? First, we know that the detect function "0x4F00"
requires a memory buffer in the low 1M memory space (DOS memory) where it
will return the mode tables, OEM strings, etc. So we would have to allocate
space under 1M, equal to sizeof(struct VBE_vInfo). We could use
"__dpmi_allocate_dos_memory()" but there is an easier way. DJGPP uses a
global transfer buffer internally, usually 4K in size, and we can use this
as our conventional memory buffer! After the call, we can just copy from
this transfer buffer to our variable. Easy, isn't it?  [GOTCHA!] ] One more
little detail, this transfer buffer is defined as "_go32_info_block" or
"__tb" in go32.h. Then "__tb & 0x0F" is just its real mode offset, and
"(__tb >> 4) & 0xFFFF" is the segment.
---------------------------------------------------------------------------
How about a VBE_getModeInfo function?

        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);
          ...
        }

---------------------------------------------------------------------------
Let's grab the linear video memory address in 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;

We have it! Using "__dpmi_physical_address_mapping()", we are able to
convert the device's physical address to a linear address than we can use
to poke around with, just like "0xA0000" with Mode 13h! However, before we
start writing to video memory, we need to enable the video mode.

        r.x.ax = 0x4F02;
        r.x.bx = 0x4101;
        __dpmi_int(0x10, &r);

---------------------------------------------------------------------------
And here is a nearptr putpixel hack!

        unsigned char *videoptr = (unsigned char *)linear_address;

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

---------------------------------------------------------------------------
Farptr access is a bit more involved than the Mode 13h version.
 [GOTCHA!] ] We no longer have a selector handy to access this linear
address. So what do we do? Easy, we make one! In addition, we set the base
address of our new selector to the value of "linear_address"; this way,
everything starts from 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);

---------------------------------------------------------------------------
Our good movedata functions are still available.

        void copy_buffer2(void)
        {
          movedata(_my_ds(), doublebuffer, our_global_selector, videoptr, width*height);
        }

---------------------------------------------------------------------------
One last thing, about the protected mode VBE protected mode interface.
First, the relevant data structure, as described in SVGAKIT.

        #pragma pack(1)
        struct VBE_PMInterface
        {
          short pfsetWindow PACKED;
          short pfsetDisplayStart PACKED;
          short pfsetPalette PACKED;
          ...
        };
        #pragma pack()

This structure will store pointers to the VBE services, if one wants to
call them directly from protected mode.

Now let's write a function that will retrieve our function pointers.

        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);
        }

One allocates a buffer equal to size "r.x.cx" bytes after the "0x4F0A"
call, then copies the protected mode interface information from DOS memory
into it. Then the pointers to the VBE functions are easily retrieved:

        vbepmi + vbepmi->pfsetWindow
        vbepmi + vbepmi->pfsetDisplayStart
        vbepmi + vbepmi->pfsetPalette

Please refer to the VBE 2.0 specifications for further information. By the
way, do not forget to copy the interface from DOS memory after EVERY mode
set, and free the buffer when shutting down the graphics system.

---------------------------------------------------------------------------

VBE 1.2

There's not much to talk about here, really. I just want to show you the
bank switching code, since we don't necessarily have the convenient linear
frame buffer as before.

        void bankswitch(short bank)
        {
          __dpmi_regs r;
          r.x.ax = 0x4F05;
          r.x.bx = 0x0000;
          r.x.dx = bank;
          __dpmi_int(0x10, &r);

         /* In djasm:
          __asm__ __volatile__("
            movw $0x4F05, %%ax;
            xorw %%bx, %%bx;
            int $0x10"
            : : "d" (bank) : "ax", "bx", "dx"
          ); */
        }

In VBE mode 101h (640x480x8), you can have each bank holding 64K (65536)
bytes. So the bank is computed as:

        short bank = (short)((640*y + x) >> 16);

To copy your double buffer to video memory using the nearptr functions, do:

        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++;

That's about it. Qapla'
---------------------------------------------------------------------------

Other resources

There are excellent graphics and game libraries available for DJGPP. Some
that I have seen are GRX, Allegro, XLIB for DJGPP, and JLIB. They are
available at x2ftp.oulu.fi and the v2apps/ directory at official DJGPP
distribution sites.
---------------------------------------------------------------------------
11/29/96 icbm@IRC

GREETS lexi
*ace*bjarne*blacky*chug*cm*dm*dM*flub*foxt*goo*griff*hh*jaw*jl*kes*lippis*mag*mblade*
*midg*qs*rad*ronski*rs*saeg*sec*ser*shiv*sigz*silvr*sledge*terran*tom*xgc*x0r*zaph*zed*zhiv*
*#c*#gamecode*#os2prog*

Thanks to all people behind DJGPP!

Mail me corrections/suggestions/complaints/crap at avly@castle.net

Copyright  1996 avly@castle.net All Rights Reserved.

All trademarks mentioned are of their respective companies.

Standard Disclaimer

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.
