FC Navigation
FortuneCity ad
FC Navigation

DJGPP 3D Polygon Tutorial Mode Z suppliment

Here's the engine from my 3dtut3 in Z mode. Although future installments of the 3D tutorials are going to stay in mode13h, I figured some folks might be able to use this....

Author: James McCue
Author e-mail: aloiterer@juno.com
Author homepage:
http://www.fortunecity.com/skyscraper/gigahertz/179/index.html
(jeez! - that's a long URL...)

1 Introduction
2 Mode Z (320x400x256)
3 differences
4 why I do vertical scan conversion
5 a discussion of triple buffering
6 Running the demo

This program and it's related resources:
  • poly3z.c
  • magic11z.c
  • magic11z.h
  • modez.c
  • modez.h
  • input.c
  • input.h
  • texturez.c
  • texturez.h
  • TimerDJ.h <-by Dhonn Lushine
  • 256x64.bal


  • ...along with any included .plgs, or pcxs
- are without warrantee of any kind. The author assumes no liability for damage that may arise via the use, or abuse, of this code...

I put the following line in Options->Compiler Options window under RHIDE:

-s -funroll-loops -fomit-frame-pointer -O2

after putting poly3z.c, modez.c, input.c, magic11z.c, and texturez.c into a RHIDE project that is...

gcc -s -funroll-loops -fomit-frame-pointer -O2 poly3z.c modez.c input.c magic11z.c texturez.c -o 3dtut3z.exe

... is another way to build this stuff...

#1 Introduction

I figured that since alot of the actual drawing routines from the little polygon engine I'm working on here have the word _Planar at the end, I'd finally show you where the heck that all comes from.

I loved what programming my vga card (and alot of other people's video cards) into planar graphics modes was able to do for me in my realmode days...
  • It saved me having to allocate space in system memory for a double_buffer.
  • It allowed me to clear out video memory four(4) pixels at a time.
  • Page flipping took my computer and video card virtually NO time compared to moving the contents of a double_buffer to the video ram.
In my Mode Y tutorials, I tried to impress upon you that there are advantages, as well as some disadvantages, to using some of these "off the beaten path" video modes. If I've failed so far - it's my fault - not yours!

The demo program to this tutorial should point up two major considerations, when you're thinking about escaping mode13h.

... and with polygon graphics, there are some special considerations.

For the demo, we're going to flip the computer into a resolution DOUBLE that of Mode13h, and use the engine that has been developed over the course of the first three (3) polygon graphics tutorials.

I'm going to do my drawing to an unseen area of video ram, and then "page-flip" to that video page, then draw on the unseen video page again... basically toggling between two video pages... thanks to mode Z's ability to give us more than 64k to work with.

I'm going to discuss double_buffering this mode as well

Actually, the kind of double_buffering I'll be muttering about (at least I think that's what they mean) is called triple_buffering, we're going to do all our drawing to a 128 thousand byte double_buffer, copy that to the UNSEEN page of video ram, and then "flip" to that page. It's alot of data to move around, but I'll discuss why you might want to consider it an option.

#2 Mode Z (320x400x256)

I guess it would be a good idea to talk about what mode Z is first... so I'll do that here, and show you the mode set for it too. You'd be suprised how much of our little engine works exactly the same, regardless of video mode that we're in.

Mode13h is a 320x400 video mode. It actually consists of 400 horizontal scan lines, but the way the vga is programmed by the bios when you normally set mode13h, horizontal lines are double scanned.

In mode Z, we reduce the number of scanlines to one. This is accomplished by programming the CRT controller.

If this looks ALOT like my mode Y set... well - it is my mode Y set - I started with this code, and commented out the part that reduces the number of scanlines to one. That's how I "discovered" mode Y for myself ; )


void Set_Mode_Z(void)
{
	// this function will set the video mode to 320x400x256

	int data;  // used to store data
	__dpmi_regs the_regs;

	// set system to mode 13h and use it as a foundation to base 320x400 mode on

	memset(&the_regs, 0, sizeof the_regs);    // is this memset really necessary???
	the_regs.x.ax = 0x13; /* 0x13 is the mode number */
	__dpmi_int(0x10, &the_regs);


	// make changes to the crt controller first

	outportb(CRT_CONTROLLER, CRT_MAX_SCANLINE);
	data=inportb(CRT_CONTROLLER+1);
	outportb(CRT_CONTROLLER+1, RESET_BITS(data,0x0f));

	// use byte addressing instead of word

	outportb(CRT_CONTROLLER,CRT_ADDR_MODE);
	data=inportb(CRT_CONTROLLER+1);
	outportb(CRT_CONTROLLER+1,RESET_BITS(data,0x40));

	// second register that needs to reflect byte addressing

	outportb(CRT_CONTROLLER,CRT_MODE_CONTROL);
	data=inportb(CRT_CONTROLLER+1);
	outportb(CRT_CONTROLLER+1,SET_BITS(data,0x40));

	// make changes to graphics controller

	// set addressing to not use odd/even memory writes

	outportb(GFX_CONTROLLER,GFX_WRITE_MODE);
	data=inportb(GFX_CONTROLLER+1);
	outportb(GFX_CONTROLLER+1,RESET_BITS(data,0x10));

	// don't chain the memory maps together

	outportb(GFX_CONTROLLER,GFX_MISC);
	data=inportb(GFX_CONTROLLER+1);
	outportb(GFX_CONTROLLER+1,RESET_BITS(data,0x02));

	// make changes to sequencer

	// again we must select no chaining and no odd/even memory addressing

	outportb(SEQUENCER,SEQ_MEMORY_MODE);
	data =inportb(SEQUENCER+1);
	data = RESET_BITS(data,0x08);
	data = SET_BITS(data,0x04);
	outportb(SEQUENCER+1,data);

	// now clear the screen

	outportb(SEQUENCER,SEQ_PLANE_ENABLE);
	outportb(SEQUENCER+1,0x0f);

	// clear the screen, remember it is 320x400, but that is divided into four
	// planes, hence we need only to clear 16 thousand bytes since there will be four planes
	// each being cleared in parallel for a total of 4*16,000 or 64000 = 320x400
	// note: "k" in this example means 1000 not 1024

	// clear BOTH pages

	rep_stosl(0x00,video_buffer,16000);

	// set screen width/height variables

	screen_width_mode = 80;
	screen_height_mode = 400;

} // end Set_Mode_Z
Now Now... before you start thinking I'm going to go on a vga programming disertation - fear not - we're going to do polygon graphics here!

You see, the all graphics that I'll be using here are stored just like they are in our work in Mode13h, the only routines I'll be using from the Mode Y tutorials are the page-flipping code, and maybe the Print_String_Mode_Y function....

In the default running of the demo program that accompanies this mess... I'm doing all my drawing directly to video memory, without using a double_buffer. The reason you can't see the imagery being drawn is because I'm drawing to an area of unseen video memory.

Part of the advantage of Planar video modes is having a full 256k of video memory to use after all...

To show the new screen-full of info... we simply flip the page - exactly like in our discussion of Mode Y

In this tutorial I'll show you some of the lowest level code that changes due to our using a planar video mode, why I use vertical scan conversion in the first place, (it started w/ planar stuff) and show you what I needed to change for the higher resolution.

I'll touch on other things too... as they come to mind... ; )

#3 Differences

Since the resolution of the screen is changing, we have to tinker w/ the define ASPECT_RATIO... and that points up an issue with all these different graphics modes.

Maybe I'm just not "of a mind" to do this properly... or maybe it's just because I've rushed too much, but I don't have things setup where - you can set to any of several video modes - and the key variables you want to change are initialized automatically, with the correct values for the video mode.

What I mean is... the list of variables (and #DEFINES) that changes if, say, you wanted to do this program in mode X (320x240x200) instead of mode z, you'd have to: (aside from getting the mode set for it ; )

goto magic11z.h - to change the:
  • ASPECT_RATIO
  • HALF_SCREEN_HEIGHT
  • ... and other defines...
and then goto magic11z.c and change:
  • poly_clip_max_y
  • and other variables.
You also may want to have as your starting addresses for your video "pages" something other than what I have set them to in modez.h... especially if you want to use the split screen stuff that I do in Mode Y tutorial 3, for a status area.

The bottom line is, the kind of coding that went into making ALLEGRO takes alot of planning...

- in fact - although my demos work and all that, I keep getting input from folks pointing up some bad patches of code, some outright mistakes... stuff like that... and even though I might not always sound it... I appreciate the input.

#4 why I do Vertical scan conversion

Here we go... this virtually the same as Vert_Scan_Text_Planar_Clip from my Third 3d tutorial. Look it over... then I'll explain.

void Vert_Scan_Text_Planar_Clip(int column, int e_y1, int e_y2, int e_u1,
                                int e_u2, int e_v1, int e_v2, int text_shade)
{
    int pu1,pu2,pv1,pv2,py1,py2;

    int i;    // looping variable
    float linewidth; // used in the final drawing of a vertical strip

    long  uwidth,
          vwidth;

    long du,dv,u_curr,v_curr;	// the current u,v texture coordinates
    
    unsigned char color_me;
    
    int     x_index,   // looping variables
    	    y_index;
    int vert_strip;          // looping variables

    unsigned char *dest_addr;

    unsigned char *text; // texture pointer

    int planar_offset;

    text = textures.frames[textures.curr_frame];

    // start off working texture coordinates for current row
    
    py1 = e_y1;
    py2 = e_y2;
    pu1 = e_u1;
    pu2 = e_u2;
    pv1 = e_v1;
    pv2 = e_v2;
    
    // sort edges...
    if(py2<py1)
    {
        i=py1;
        py1=py2;
        py2=i;
    
        i=pu1;
        pu1=pu2;
        pu2=i;
    
        i=pv1;
        pv1=pv2;
        pv2=i;
    }
    
    // assign starting UxV coordinates
    
    u_curr = pu1<<16;
    v_curr = pv1<<16;
    
    linewidth=py2-py1;
    uwidth=pu2-pu1;
    vwidth=pv2-pv1;
    
    // calculate du,dv with respect to y
    
    du=(uwidth<<16)/(linewidth+1);
    dv=(vwidth<<16)/(linewidth+1);
    
    // clip texture as needed
    
    if (py1<0)
    {
        u_curr += (du * (float)(-py1));
        v_curr += (dv * (float)(-py1));
    
        py1 = 0;
    }
    
    if (py2 >=screen_height) py2 = (screen_height-1);
    
    // compute offset in the double buffer

    planar_offset = (column & 0x03);

    outportb(SEQUENCER,SEQ_PLANE_ENABLE);
    outportb(SEQUENCER+1,(0x01<<planar_offset));

    dest_addr = video_buffer+((int)py1<<6)+((int)py1<<4)+(column>>2);
    
    // render a verticle strip already!
    for (vert_strip=(int)py1; vert_strip <= (int)py2; vert_strip++)
    {
        // extract coordinates where a color will be copied
        // from in the texture...
    
        x_index = (int)(u_curr>>16);
        y_index = (int)(v_curr>>16);

       // extract pixel

        color_me = text[(y_index<<y_texture_shift) + x_index];
    
    // but this time, use it as part of an index into our
    // new color_table
    
        color_me = color_table[(int)color_me][text_shade];
    
        // plot the point
    
        *dest_addr = color_me;
    
        // move one line down in the video_buffer
    
        dest_addr += 80;
    
        // move to the next next (u,v) coordinate for the
        // texture to be used in this strip
    
        u_curr+=du;
        v_curr+=dv;
    }
} // end Vert_Scan_Text_Planar_Clip
planar_offset is a new variable in this routine. - and by the way, if you've compiled some of my code with the -Wall switch... you know I have a penchant for leaving unused variables in many of my routines... I hope that hasn't confused you!

planar_offset is used to help set the PLANE we're going to be drawing to.

If you recall my mode Y stuff, you remember that in these mode X video modes, we have four(4) non-overlapping "pixel planes". Pixel 0,0 belongs to plane 0... pixel 3,24 belongs to plane 3... etc...

Recall that the y coordinate of the pixel we want to plot is irrelevant, only the x coordinate determines the plane a pixel will be plotted to.

To set the proper plane we MOD the column we're going to draw to by 4, and then use that value to shift a 1 into proper position to enable a particular plane, so we can draw to it.

Here are the three lines I use to do that:

    planar_offset = (column & 0x03);

    outportb(SEQUENCER,SEQ_PLANE_ENABLE);
    outportb(SEQUENCER+1,(0x01<<planar_offset));
Pixels are plotted EXACTLY as they are in Mode Y:

    dest_addr = video_buffer+((int)py1<<6)+((int)py1<<4)+(column>>2);
Instead of adding 320 to move down a column of pixels, we add 80.

If we were doing horizontal scan conversion of our polygons, those two OUTS would have to be repeated for every pixel we would be drawing.

- and coupled with the "overdraw" that goes on with the type of rendering we're doing here - that's alot of OUTS.

By doing our renderings with vertical scan conversion, we can take those OUTS outside of the inner loop, seriously reducing the number of OUTS we need. OUTS take several computer "cycles" to complete. To be honest, I don't know what a cycle is equal to in time off-hand ; )

Of course, we could use a double_buffer (in several ways in fact) to draw to, and then just copy it over to the video memory in 8 BYTE OUTS, but there are things to consider before trying that, and the results might vary, depending on several factors.

#5 a discussion of triple buffering

Copying 128 thousand bytes of double_buffer obviously won't be as quick as it was with 64 thousand bytes, and a double_buffered version of this same demo program (that you're hopefully going to check out) couldn't update fast enough to be invisible even on my friend's fast comp...

The solution is to keep using the page flipping - and copy the double buffer to the unseen area of video ram, and then flipping the page to it.

Unless I have a fundamental misunderstanding, and I usually do, of different terms people use, this is what triple buffering is.

Here, I'll allocate the double buffer:

if (!(double_buffer=(char *)malloc( (320*400) + 1 )) )
   // get out
Here's what the pixel plotting setup becomes in Vert_Scan_Text_Planar_Clip:

    planar_offset = (column & 0x03) * 32000;

    dest_addr = double_buffer+((int)py1<<6)+((int)py1<<4)+(column>>2) + planar_offset;
Basically, I go to the (perhaps unuseful) trouble of "pretending" that the double_buffer is planar in nature.

Why not, it's system memory - you can organize it - or disorganize it - however you want. Just make sure you allocate it... and that you stay inside that allocated memory. Dhonn once again has pointed out some places where I missed that, but RHIDE protected me from my mistakes.

By pretending the memory is planar, I can copy my double_buffer to the unseen video page with four(4) memcpys, or any faster routines you might want to use. (like the 32-bit inline assembly macro I use all the time)

        outportb(SEQUENCER, SEQ_PLANE_ENABLE);
        outportb(SEQUENCER+1, 0x01);
        rep_movsl(double_buffer, video_buffer, 8000);
        outportb(SEQUENCER, SEQ_PLANE_ENABLE);
        outportb(SEQUENCER+1, 0x02);
        rep_movsl(double_buffer+32000, video_buffer, 8000);
        outportb(SEQUENCER, SEQ_PLANE_ENABLE);
        outportb(SEQUENCER+1, 0x04);
        rep_movsl(double_buffer+64000, video_buffer, 8000);
        outportb(SEQUENCER, SEQ_PLANE_ENABLE);
        outportb(SEQUENCER+1, 0x08);
        rep_movsl(double_buffer+96000, video_buffer, 8000);
Notice I'm using 8000 there... that's because (gulp! I hope!) I'm doing the memcpy w/ QUADS!

You'd insert that code somewhere in the pipeline after you've set the working page, and before you've switched the visual page to it.

Using this triple_buffered technique actually ran a bit slower on my computer than the simple pageflipping version.

- but in situations where you have ALOT of overdraw - the triple_buffered approach could be the way to go for you!

#6 Running the demo

I was going to give you the option of switching between drawing directly to video memory combined with page flipping, AND using a triple buffered approach, but this time around I'm just going to do the page-flipping version.

Perhaps I should take ALL the actual drawing routines out of Magic11z.c ... so I could then just make a separate module for the triple buffered versions of the drawing functions, and it would be easier to compare the two.

I guess all this studying I'm doing for school is holding me back on that, but Novell software knowledge could be useful in getting me some coin one of these days... I hope to really become PROLIFIC over the summer ; )

The keys to the demo are: (and it figures, because the demo is virtually the same as 3dtut3's demo!)

w,e,r,s,d,f <= rotation keys
UP,DOWN <= Move the ship forward or back along it's NOSE VECTOR (i.e.: fly?)

1,2,3,4,5,6 <= change the modeling mode
p,o,i,u <= change the texture used in texturemapped modes

C <= toggle rotation mode (starts as mine, goes back to standard)
ESC: Quits!

For this demo, I'm actually using a bigger texture, 128x64 ... for most of the facets of this spaceship that's flying around... so check it out. And don't forget to look at my notes about compiler options to use with these 3d tutorial demos to make them faster.

Thanks and check out the demo

Well... let's see, what's up next for the 3d tutorials... hmmmm....

I think by now you've noticed a flaw with the average z polygon sorting stuff I've been doing in all these tutorials. (sometimes the sorting "breaks down" and you can see a polygon face you really shouldn't be able to see... at least not on top of everything else) You can construct your objects to go around that limitation, but eventually, you're going to need to employ other methods.

Since I already have some code for doing a vertical scanline z-buffer, I figure I'll add that stuff to the 3dtut engine, and explain how that works...

I'll definitely be getting more into clipping as well, because we're definitely going to need it.

hmmm... maybe some cool textured transparency effects... flat shaded is pretty easy...

Download the Z mode demo & source code


Jim the loiterer
aloiterer@juno.com
http://www.fortunecity.com/skyscraper/gigahertz/179/index.html


Copyright 1998 - by James McCue



LinkExchange
LinkExchange Member



[ Homepage | my Games | C Programming | VB Programming | Links ]

This Site Made By James McCue (C) copyright 1997,1998