[comp.unix.i386] SCO EGA/VGA Graphics programming example

chapman@sco.COM (Brian Chapman) (07/01/90)

Here is the ~300 line example of EGA/VGA graphics on SCO SystemV
I promised.  It is 350 lines, or just over 400 if you count the
Makefiles.  I include two Makefiles because Unix 3.2 has features
that don't exist in Xenix, and I don't like how messy "portable"
Makefiles get.

This program does it all,
	Mode switches.
	Memory map the frame buffer
	Direct INs and OUTs to the EGA/VGA board.
	Screen Switch signals

This program should fill the screen with a solid color
one pixle at a time in a random order.  It should cycle
through 16 colors, black fades to blue, blue fades to green... etc.

I will answer questions about the SCO video ioctls.

I will not answer questions about how the EGA/VGA HW works.
It is very complex, I am not the world's greatest expert
and there are some very good books on the market.
	-- Chapman

---- Cut here ---- You know the drill. -----

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# Makefile.unix Makefile.xenix dissolve.c ega.c portio.s scovid.c video.h

echo x - Makefile.unix
cat > "Makefile.unix" << '//E*O*F Makefile.unix//'
# Remove UNIX internationlization (makes binary smaller)
NOINTL= -nointl
# Unix shared library link (makes binary smaller)
LIBS= -lc_s

PROF= #-p
STRIP= -s

CFLAGS= -O ${PROF}
LDFLAGS= ${PROF} ${STRIP} ${NOINTL}
AS= masm
ASFLAGS= -Mx
CVTOMF=cvtomf

.s.o:
	${AS} ${ASFLAGS} -o$@ $<
	${CVTOMF} $@

dissolve: dissolve.o scovid.o ega.o portio.o
	cc ${LDFLAGS} -o dissolve dissolve.o scovid.o ega.o portio.o ${LIBS}
	lc -l dissolve

dissolve.o: dissolve.c video.h

scovid.o: scovid.c video.h

ega.o: ega.c video.h
//E*O*F Makefile.unix//

echo x - Makefile.xenix
cat > "Makefile.xenix" << '//E*O*F Makefile.xenix//'
PROF=
STRIP= -s

CFLAGS= -O ${PROF}
LDFLAGS= ${PROF} ${STRIP}
AS= masm
ASFLAGS= -Mx

.s.o:
	${AS} ${ASFLAGS} -o$@ $<

dissolve: dissolve.o scovid.o ega.o portio.o
	cc ${LDFLAGS} -o dissolve dissolve.o scovid.o ega.o portio.o
	lc -l dissolve

dissolve.o: dissolve.c video.h

scovid.o: scovid.c video.h

ega.o: ega.c video.h
//E*O*F Makefile.xenix//

echo x - dissolve.c
cat > "dissolve.c" << '//E*O*F dissolve.c//'
#include <sys/fcntl.h>
#include <sys/errno.h>

#include "video.h"

randmask[33] =	{
		-1,		-1,		0x3,		0x6,
		0xc,		0x14,		0x30,		0x60,
		0xb8,		0x110,		0x240,		0x500,
		0xca0,		0x1b00,		0x3500,		0x6000,
		0xb400,		0x12000,	0x20400,	0x72000,
		0x90000,	0x140000,	0x300000,	0x420000,
		0xd80000,	0x1200000,	0x3880000,	0x7200000,
		0x9000000,	0x14000000,	0x32800000,	0x48000000,
		0xa3000000
};

main()
{
	int i, rc, c;
	extern errno;

	graf();
	for(i=1; i<=16; i++)
		dissolve(i, LPS, PPL);
	grafend();
}

dissolve(color, height, width)
int height, width;
{
	int pixels, lastnum;		/* last pixel's number */
	int regwidth;			/* width of sequence generator */
	register int mask;		/* mask to XOR w/ to create sequence */
	register unsigned int element;	/* one element of random sequence */

	pixels = height * width;
	lastnum = pixels - 1;
	regwidth = bitwidth(lastnum);
	mask = randmask[regwidth];

	element = 1;
	do {
		if(element <= lastnum)
			ega_bit(color, element);
		
		if(element & 1)
			element = (element >> 1) ^ mask;
		else
			element >>= 1 ;
	} while (element != 1);

	ega_bit(color, 0);
}

bitwidth(x)
{
	int b, w, i;

	i = w = 0;
	b = 1;
	for(i=0; i<32; i++)
	{	if(x & b)
			w = i;
		b <<= 1;
	}
	return(w+1);
}
//E*O*F dissolve.c//

echo x - ega.c
cat > "ega.c" << '//E*O*F ega.c//'
/*
 * EGA 640x350 16 color, 4 plane frame buffer
 * accessing code.  This also maintains the incore
 * image when the screen is not displayed.
 */

#include "video.h"

#define PLANESZ  (BPL * LPS)
#define FRAMESZ  (PLANESZ * 4)

/*
 * IO addresses
 */
#define GC_INDEX  0x3CE		/* Graphics controller index register    */
#define GC_MAP    0x4		/* index for mapping the READ plane      */
#define GC_MODE   0x5		/* index for setting the READ/WRITE mode */
#define GC_MASK   0x8		/* index for setting WRITE bit mask      */

#define SQ_INDEX  0x3C4		/* Sequence controller index register */
#define SQ_MAP    0x2  		/* index for mapping the WRITE plane  */

char *scr_buf;

ega_save()
{
	register int i;

	for(i=0; i<4; i++)
	{
		out_index2(GC_INDEX, GC_MAP, i);
		memcpy(scr_buf+(i*PLANESZ), Screenmem, PLANESZ);
	}
	out_index2(SQ_INDEX, GC_MAP, 0);
}

ega_restore()
{
	register int i;

	ioctl(0, GRAPHICS_MODE, (char *)0);
	for(i=0; i<4; i++)
	{
		out_index2(SQ_INDEX, SQ_MAP, 1<<i);
		memcpy(Screenmem, scr_buf+(i*PLANESZ), PLANESZ);
	}
	out_index2(SQ_INDEX, SQ_MAP, 0x0F);
	out_index2(GC_INDEX, GC_MODE, 2);	/* write mode 2 */
}

ega_grafmode()
{
	if(-1 == ioctl(0, GRAPHICS_MODE, (char *)0))
	{
		perror("graphics mode");
		exit(1);
	}
	out_index2(GC_INDEX, GC_MODE, 2);	/* write mode 2 */
/*
 * Allocate and init the save screen data structure.
 */
	scr_buf = (char *)malloc(FRAMESZ);
	memset(scr_buf, '\0', FRAMESZ);
}

ega_bit(color, offset)
register int offset;
{
	register char *des;
	register int junk;

	while(!Isdisplayed)
		pause();

	des = &Screenmem[offset >> 3];
/*
 * Using write mode 2
 */
	out_index2(GC_INDEX, GC_MASK, 0x80 >> (offset&0x07) );
	junk = *des;		/* latch the data */
	*des = color;
}
//E*O*F ega.c//

echo x - portio.s
cat > "portio.s" << '//E*O*F portio.s//'
;	Static Name Aliases
;
	TITLE   video

	.386
_TEXT	SEGMENT  BYTE PUBLIC 'CODE'
_TEXT	ENDS
_DATA	SEGMENT  WORD PUBLIC 'DATA'
_DATA	ENDS
CONST	SEGMENT  WORD PUBLIC 'CONST'
CONST	ENDS
_BSS	SEGMENT  WORD PUBLIC 'BSS'
_BSS	ENDS
DGROUP	GROUP	CONST,	_BSS,	_DATA
	ASSUME  CS: _TEXT, DS: DGROUP, SS: DGROUP, ES: DGROUP
_TEXT      SEGMENT

	public	_out_index2

_out_index2		proc near
	push	ebp
	mov	ebp,esp

	mov	edx,[ebp+8]
	mov	al,[ebp+12]
	mov	ah,[ebp+16]
	out	dx, ax

	leave	
	ret	
_out_index2	endp
_TEXT	ENDS
END
//E*O*F portio.s//

echo x - scovid.c
cat > "scovid.c" << '//E*O*F scovid.c//'
#include <stdio.h>

#include <sys/types.h>
#include <sys/signal.h>
#include <sys/vtkd.h>

#include "video.h"

#define SIG_REL	SIGUSR1
#define SIG_ACQ	SIGUSR2

/*
 * internal only symbols.
 */
void rel_screen(), acq_screen(), grafquit();
int Oldmode;			/* save mode of user shell screen */

/*
 * Set up the graphics multiscreen stuff and call another
 * routine to set up card.
 */
graf()
{
	struct vt_mode smode;

	Isdisplayed = 1;
/*
 *  Set up to catch the screen switch signals.
 */
	signal(SIG_REL, rel_screen);
	signal(SIG_ACQ, acq_screen);
	signal(SIGINT, grafquit);
/*
 * Set up the data structure that asks the driver
 * to send you signals when the screens are switched.
 * mode == VT_PROCESS means send screen switch signals.
 * mode == VT_AUTO means turn off screen switch signals (regular mode).
 * relsig == the signal you want when the user switches away.
 * acqsig == the signal you want when the user switches back to you.
 */
	smode.mode = VT_PROCESS;
	smode.waitv = 0;	/* not implemented, reserved */
	smode.relsig = SIG_REL;
	smode.acqsig = SIG_ACQ;
	smode.frsig  = SIGINT;	/* not implemented, reserved */

	if(-1 == ioctl(0, VT_SETMODE, &smode))
	{
		perror("screen switch signal ioctl VT_SETMODE");
		exit(1);
	}
	grafmode();
}

/*
 * this is the signal handler for when the user screen flips
 * away from us.
 */
void
rel_screen()
{
	signal(SIG_REL, rel_screen);
	Isdisplayed = 0;
	ega_save();
/*
 * Tell the video driver that you have saved your state
 * and it can now have the card to switch to the new screen.
 * The video driver waits (forever) for this ioctl before
 * it will complete the screen switch requested by the user.
 * If you don't make this ioctl the screen switcher will
 * be wedged until it gets one.  It is best to have a
 * small one line reldisp.c program to unwedge your screen
 * switcher when development programs screw up from time
 * to time.
 */
	ioctl(0, VT_RELDISP, VT_TRUE);
}

/*
 * this is the signal handler for when the user screen flips
 * back to us.
 */
void
acq_screen()
{
	signal(SIG_ACQ, acq_screen);
	Isdisplayed = 1;
	ega_restore();
/*
 * Tell the video driver that you have restored your state
 * and screen switching can now continue.
 */
	ioctl(0, VT_RELDISP, VT_ACKACQ);
}

void
grafquit()
{
	grafend();
	exit(0);
}

/*
 * restore text mode.
 */
void
grafend()
{
	ioctl(0, MODESWITCH | Oldmode, (char *)0);
}

grafmode()
{
	int adapter, privlcmd;
/*
 * Confirm that we are on a supported video adapter.
 */
	adapter = ioctl(0, CONS_CURRENT, (char *)0);
	if(EGA != adapter && VGA != adapter)
	{
		puts("Stdin must be an EGA or VGA multiscreen\n");
		exit(0);
	}
/*
 * Save the user's current text mode so you
 * can restore it on exit.
 */
	Oldmode = ioctl(0, CONS_GET, (char *)0);
/*
 * Get privledge to do direct INs and OUTs to the video card.
 */
	if(EGA == adapter)
		privlcmd = EGA_IOPRIVL;
	else
		privlcmd = VGA_IOPRIVL;
	if(-1 == ioctl(0, privlcmd, 1))
	{
		perror("I/O privilege denied");
		exit(1);
	}
/*
 * Have the video driver reprogram the card for EGA 640x350 16 color mode.
 */
	ega_grafmode();
/*
 * Map the video card's frame buffer into your address space.
 * This must be done after the mode switch command or you get
 * frame buffer address for the wrong mode mapped in.
 */
	Screenmem = (char *)ioctl(0, MAPCONS, (char *)0);
}
//E*O*F scovid.c//

echo x - video.h
cat > "video.h" << '//E*O*F video.h//'
#ifndef SW_ENH_CG640
#include <sys/machdep.h>
#endif

#define VGA_DEMO

#ifdef VGA_DEMO
 #define GRAPHICS_MODE	SW_VGA12	/* VGA 640x480 16 colors */
 #define LPS	(480)			/* lines per screen	*/
#endif

#ifdef EGA_DEMO
 #define GRAPHICS_MODE	SW_ENH_CG640	/* EGA 640x350 16 colors */
 #define LPS	(350)			/* lines per screen	*/
#endif

#define PPL	(640)			/* pixels per line	*/
#define BPL	(PPL/8)			/* bytes per line	*/

/*
 * externaly availible symbols.
 */
int Isdisplayed;			/* flag: when are we flipped away */
char *Screenmem;			/* physical map to the video RAM  */
int graf();				/* Set everything up		  */
void grafend();				/* Restore user's text mode	  */
void grafquit();			/* Clean-up and exit		  */
//E*O*F video.h//

exit 0
-- 
Brian Chapman		uunet!sco!chapman
Pay no attention to the man behind the curtain!