www.delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp/1997/09/19/02:02:16

From: pjfarley AT dorsai DOT org (Peter J. Farley III)
Newsgroups: comp.os.msdos.djgpp
Subject: Re: graphics programming tutorial
Date: Wed, 17 Sep 1997 01:03:15 GMT
Organization: None
Lines: 471
Message-ID: <341f2be5.5393714@snews.zippo.com>
References: <341e9b5c DOT 12935450 AT news DOT demon DOT co DOT uk>
NNTP-Posting-Host: news.newsdawg.com
To: djgpp AT delorie DOT com
DJ-Gateway: from newsgroup comp.os.msdos.djgpp

steven AT thurgood DOT demon DOT co DOT uk ( Steven Thurgood) wrote:
> There is a link in the FAQ to a graphics programming tutorial.
>Unfortunately the link seems dead, so could anybody please point me
>towards a working link for this document? Or help me with my problem.
>I am moving from msvc2, and having a bit of trouble.
>Is there any docs giving info on making such a move? Or at least
>something about programming in protected mode?
>TIA

Steve,

This was posted to the newsgroup a while back, and I saved it.

-----------------message--------------------------------------
I found this on some odd web page, its actually by a guy named ICBM,
I'm
taking no credit, just posting this to benefit the general public. It
answers often asked questions about graphics in DJGPP.

-- BEGIN --
This is to help all the people who are new to DJGPP. They are probably
used
to a 16 bit compiler, and real mode memory models. They have lots of
problems
and questions concerning their old graphics routines in DJGPP.

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

-- END --


----------------------------------------------------
Peter J. Farley III (pjfarley AT dorsai DOT org)

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019