ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The PC GAMES PROGRAMMERS ENCYCLOPEDIA 1.0 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Well, here it is! This is the first edition of the PC Games Programmers Encyclopedia. The PC-GPE as it currently stands is a collection of text files, each covering a different aspect of programming games for the PC. Some files were obtained from the net, others were grabbed off Usenet, quite a few were written for the PC-GPE. Every effort has been made to contact the original authors of all public domain articles obtained via ftp. In some cases the original authors were not able to be contacted. Seeing as these files were already available to the public the liberty was taken to include them anyway. The files were not modified in any way. There is a list at the end of this document showing which files we couldn't contact the authors about. Please note that files were *not* written exclusively for the PC-GPE unless stated otherwise. The information in the PC-GPE is provided to you free of charge. The authors of each article have included their own conditions of use, eg some ask that you give them credit if you use their source code. As a general rule of thumb, an e-mail or postcard to an author telling them you found their file helpful probably wouldn't go astray..... This first version of the PC-GPE is very hardware oriented. We hope to include more actual game algorithms in future releases. If you would like to see a particular topic included in the next PC-GPE release or if you think you could contribute an article then by all means let us know (btw plugs for personal projects in articles are accepted). The editor's e-mail address is at the end of this file. Some of the text files are pretty long, so the PCGPE uses a protected mode file viewer (PCGPE.EXE) which may play up when run on 286 machines. If this happens read the DPMIUSER.DOC file for help on fixing the problem. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ PC-GPE Home Site ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Games Programmers Encyclopedia official home site is: teeri.oulu.fi /pub/msdos/programming/gpe There are plans to develop GPE's for the mac and other architectures for cross-platform game development. The teeri site will also hold PC-GPE updates/bug fixes/etc. Many thanks to Jouni Miettunen for all his help and for allowing us to use teeri as the PC-GPE's home site. He's put a lot of work into it and it's a great programming resource, particularly for people wanting to develop game software. ÚÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ History ³ ÀÄÄÄÄÄÄÄÄÄÙ The PC-GPE was conceived, designed and largely built by the same people who keep the Usenet groups rec.games.programmer and comp.graphics.algorithms alive. It was noticed that information required for even the most basic game development was strewn out across the vast wastelands of the Internet and was time consuming and annoying (if not down-right impossible) to obtain. Most of us can't afford to go out and buy a book every time we want to look up a particlar topic, so a bunch of us decided to grab the most commonly sought-after free info and put it in one place. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ The People Who Did All the Work ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ First a big thanks goes to everyone who wrote articles or allowed us to use their existing articles. Also thanks to the Demo groups Asphixia and VLA (more specifically Lithium and Denthor) for letting us use the asm and vga trainers they wrote. A number of people who didn't actually write articles contributed heaps to the project right from the start with tips/comments/suggestions etc as well as lots of info on where we could get stuff. Thanks go to Bri, Dizzy, Claus Anderson, Nathan Clegg, Alex Curylo, Cameron Grant, Chris Matrakidis and the many others who sent info. If it wasn't for them you probably wouldn't be reading this now! And finally thanks to Jouni Miettunen for setting up the PC-GPE directory on the teeri site, letting us use it as the official home site and supplying a heap of information. The editor would also like to thank the scores of other people who e-mailed him with suggestions, comments, requests etc...and continually hassled him to hurry up and get the damn thing finished. ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ It's a pity we live in a world where the following kind of crap is neccessary. Oh well, here goes.... Each article appearing in the PC-GPE is bound by any disclaimer that appears within it. The editor assumes absolutely no responsibility whatsoever for any effect that this file viewer or any of the PC-GPE articles have on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. The accuracy of the information contained is subject to conjecture. Use all information at your own risk. The file PC-GPE.EXE may not be distributed without all the original unmodified PC-GPE articles. The distribution rights of individual articles is at the discretion of the authors. ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ File List ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ The following is a list of all the PCGPE 1.0 files: File Description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ PCGPE EXE * PC-GPE main exe file DPMIUSER DOC * PC-GPE.EXE DPMI info file RTM EXE * PC-GPE.EXE DPMI support file RTMRES EXE * PC-GPE.EXE DPMI support file DPMIINST EXE * PC-GPE.EXE DPMI support file DPMILOAD EXE * PC-GPE.EXE DPMI support file DPMI16BI OVL * PC-GPE.EXE DPMI support file README TXT * PC-GPE main info doc FTPSITES TXT List of FTP sites for game development programs/utils ASMINTRO TXT * VLA's assembly tutorial intro file ASM0 TXT * VLA's assembly tutorial ASM1 TXT * VLA's assembly tutorial ASM2 TXT * VLA's assembly tutorial ASM3 TXT * VLA's assembly tutorial ANSI TXT * VLA's assembly tutorial support file INTEL DOC List of op codes plus timing info up to 486 CPUTYPE TXT * Testing CPU type TIMER ASM * Testing CPU speed TUT1 TXT * Asphixia's VGA Primer - Mode 13h TUT2 TXT * Asphixia's VGA Primer - Palette/Fading TUT3 TXT * Asphixia's VGA Primer - Lines/Circles TUT4 TXT * Asphixia's VGA Primer - Virtual Screens TUT5 TXT * Asphixia's VGA Primer - Scrolling TUT6 TXT * Asphixia's VGA Primer - Look-up Tables TUT7 TXT * Asphixia's VGA Primer - Animation TUT8 TXT * Asphixia's VGA Primer - 3D/Optimisation TUT9 TXT * Asphixia's VGA Primer - 3D Solids TUT10 TXT * Asphixia's VGA Primer - Chain 4 mode COPPER PAS * Asphixia's VGA Primer - Copper Effect WORMIE PAS * Asphixia's VGA Primer - Worm Effect PALLETTE COL * Asphixia's VGA Primer support file SOFTROCK FNT * Asphixia's VGA Primer support file MODEX TXT * Introduction to mode x SCROLL TXT * VGA scrolling VGAREGS TXT * VGA palette and register set VGABIOS TXT VGA BIOS function call list SVGINTRO TXT * SVGA - Intro to programming SVGA cards VESASP12 TXT SVGA - The VESA standard ATI TXT * SVGA - Programming the ATI chip set CAT TXT * SVGA - Programming the Chips & Technologies chip set GENOA TXT * SVGA - Programming the Genoa chip set PARADISE TXT * SVGA - Programming the Paradise chip set TRIDENT TXT * SVGA - Programming the Trident chip set TSENG TXT * SVGA - Programming the Tseng chip set VIDEO7 TXT * SVGA - Programming the Video7 chip set XTENDED TXT * SVGA - 640x400x256 with no bank switching 3DROTATE DOC * VLA's three dimensional rotations for computer graphics 3DSHADE DOC * VLA's three dimensional shading in computer graphics PERSPECT TXT * Perspective transforms BRES TXT * Bresenham's line and circle algorithms CONIC CC * A bresenham-like general conic sections algorithm BSP TXT * A Simple Explanation of BSP Trees TEXTURE TXT * Texture mapping FDTM TXT * Real-time free direction texture mapping STARS TXT * VLA's programming star fields FIRE TXT * Programming fire effects PCX TXT PCX graphics file format BMP TXT BMP graphics file format GIF TXT BMP graphics file format IFF DOC IFF/LBM graphics file format FLI FOR FLI/FLC graphics file format SPEAKER TXT * Programming the PC speaker (inc 8-bit sample playback) GAMEBLST TXT * Programming the GameBlaster sound card ADLIB TXT Programming the Adlib sound card SBDSP TXT * Programming the SoundBlaster sound card (DSP) SBPRO TXT * Programming the SoundBlaster Pro sound card GUSFAQ TXT * The GUS sound card's Frequently Asked Questions GUS TXT * Programming the GUS sound card MODFORM TXT * The MOD sound file format VOC TXT The VOC sound file format WAV TXT * The WAV sound file format CMF TXT * The CMF sound file format MIDI TXT * The MID sound file format UT TXT The UltraTracker sound file format SURROUND TXT Generating surround sound MOUSE TXT * Programming the mouse, general info GMOUSE DOC Mouse driver function call list KEYBOARD TXT * Programming for the PC keyboard JOYSTICK TXT * Programming for the PC joystick GAMEPAD TXT * Programming for the Gravis GamePad and Analog Pro LIMEMS41 DOC EMS (Expanded Memory Specification) XMS30 TXT XMS (Extended Memory Specification) DMA_VLA TXT * Intro to DMA PIT TXT * Programming the Intel 8253 Programmable Interval Timer DOOM TXT * DOOM techniques An asterix (*) indicates files which were either written for the PC-GPE or included with permission from the author. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Final Words from the Editor ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Greetz ÄÄÄÄÄÄ Zob: Whaddaya mean you can't come out drinking with us for 6 months? What the hell is "glandular fever" anyway? Wookie: Whaddaya mean I can't play ModemDOOM on a 2400? Fink: Live fast, die young, have a good lookin' corpse! MainFrame, bri_acid, wReam, Nocturnus, MArtist, RetroSpec, Matrix, Syntax, Andrez, Gideon and the rest of the #coders gang : try and get some sleep some time guys! Eyre: You/me babe, how 'bout it? Aggi: Remember, reality is mass hallucination resulting from alcohol deficiency! Fetish: You know the routine hon, pick a number and join the queue like the rest of 'em! Why all my code is in Pascal ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ok, ok, I'm expecting to get lots of crap over this one. To put it simply Pascal is close to psuedo code and I wanted the routines to be understood by everyone, Pascal programmers, C programmers and *REAL* (to wit, asm) programmers alike. Apart from that I'm running a 40Mg doublespaced hard drive and I have to use the fastest compiler possible. That's a good enough reason isn't it?....people?..... Shameless Plug ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are two things in life I really can't stand, 1) My ex-girlfriend 2) Being unemployed, which I am now! So if your company has any openings I'd really like to hear from you, particularly if you develop game software. I'm a 3rd year computer engineering student and my specialties lie in computer graphics and low-level PC hardware programming. I program in C++ (Dos and Windows), Pascal, 80x86 assembly, QBasic (heh heh) and Prolog. Mark Feldman Internet: u914097@student.canberra.edu.au myndale@cairo.anu.edu.au ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ A note from CyberRax (the guy who merged all the textfiles into this BIG doc and erased the executables): the following files - TIMER.ASM, PALETTE.COL, SOFTROCK.FNT, COPPER.PAS and WORMIE.PAS - have been compressed with PKZIP, tansformed into UUE format and included in the very end of this file. To use them you must extract the UUE file, uuDEcode it, and uncompress with PKUnZIP or InfoZIP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Resource Guide for Computer Game Developers This lists various libraries, development systems, and source packages available on the Internet via anonymous FTP to game development designers and programmers. Comments: game-dev@netcom.com Copyright 1993, Tom Czarnik -------------------------------------------------------------------------------- SECTIONS ======== (1) Painting, Sprite Designers (2) Sound Development (3) Development Libraries or Systems (4) Source Code Definitions: archives ===================== funet Finland nic.funet.fi:/pub/msdos/games/programming garbo-tp Finland garbo.uwasa.fi:/pc/turbopas USA ftp.wustl.edu:/mirrors/garbo.uwasa.fi/pc/turbopas netcom USA ftp.netcom.com:/pub/profile/game-dev/tools USA ftp.uwp.edu:/pub/msdos/games/game-dev/tools Definitions: system notations ============================= MS MSDOS MC Macintosh WIN MS Windows 3.x UX Unix/X-Windows AM Amiga ST Atari ST ------------------------------------------------------------------------------ (1) Painting, Sprite Designers ============================== * VGA256 [MS] A 256 color VGA paint program. Archives: netcom, funet Distribution: 256paint.zip * Sprite Designer v0.8 [MS] A sprte designer for ModeX or VGA256 Archives: funet, netcom Distribtuion: sprdes.zip * Tile256 v1.0 [MS] A VGA256 tile and map editor Archives: netcom Distribibution: tile11.zip (2) Sound Development ===================== * Blast v1.3 [MS] Sound Blaster utility and MOD player. Includes binaries, documentation, and objects for Turbo C/C++. Archives: funet, netcom Distribution: blast13.lzh * FMPlay v1.0 [MS] An Adlib and SoundBlaster FM toolkit with background player. A .rol to .scr convertor is included. Borland C 3.0 source available. Archives: netcom Distribution: fmplay10.zip (3) Game Development Libraries or Systems ========================================= * Animation Contruction Kit 3-D [MS] A package for making Wolf3D style games. Archives: funet, netcom Distribution: ackkit.zip Main development system acksrc.zip Borland C and Microsoft assembly sources pcx2img.zip PCX to IMG conversions for ACK3D * ANIVGA v1.1 [MS] Sprite routines, editor, examples, VGA256 binaries with Turbo Pascal 6.0 source. Archives: funet Distribution: anivga11.zip * DCGames v3.1 [MS] Graphics adventure and RPG building system. Includes example games. Archives: netcom Distribution: dcg301.zip Main system dcg301sb.zip SoundBlaster support dcg301xb.zip Voice and music files dcg301ut.zip Utilities dcg301up.zip Upgrade from v3.0 to v3.1 only * EGOF v1.0 [MS] A Turbo/Borland Pascal 7.0 programming library for VGA256, ModeX, VESA graphics. Documentation included. Archives: garbo-tp Distribution: egof10.zip Main library egof10b.zip Demo PCX files for egof10.zip egof10m.zip Postscript manual for EGOF v1.0 egof10p.zip Protected mode units for EGOF v1.0 * Hobbes Library v3.0 [MS] A modeX library for VGA256. No documentation available. Inlcudes Borland C and assembly. Archives: funet, nectom Distribution: hobbs3.zip Main system hobbspr2.zip Related examples. C++ source/sprite executable * SPX v1.0 [MS] A VGA256 game library for Turbo Pascal 6.0 and 7.0 by Scott Ramsay. Successor to GameTP libray. Archives: funet, netcom Distribution: spx10.zip spxdemo1.zip Demos with binary and source * Unchain v2.1 [MS] A Planar VGA Mode (aka modeX) Enforcer for Borland's IDE by Colin Buckely Archives: netcom Distribution: unchain2.zip * VGA Graphics Library v2.0 [MS] A VGA graphics library by Scott Ramsay. C and assembly source included. Archives: netcom Distribution: vgl20.zip * Wordup Graphics Toolkit v3.5 [MS] A VGA256 sprite library by Barry and Chris Egerter. Shareware Archives: funet, netcom Distribution: wgt35.zip Main system wgtdemo1.zip Demo wgtmap15.zip Map editor v1.5 wgtspr35.zip Sprite editor v3.5 * XLib v4.0c [MS] A VGA256 ModeX sprite library by Themie Gouthas. Includes Turbo C/TASM source. Free. Archives: funet, netcom Distribution: xlib04c.zip Main system xlibtool.zip Bitmap conversion tools for XLib * yakIcons v2.4 [MS] A VGA256 sprite library with higher level routines over xLib, by Victor Putz. Borland C++ 3.1 source, with doumentation. Free shareware. Archives: netcom Distribution: yak24pat.zip Patch to yakIcons v2.3 yakdemo.zip Demo of yakIcons yaktool.zip Converts PCX to .yak and .ypl yicons24.zip Main system (4) Source Code =============== * Michael Abrash's Columns on Graphics Programming from Dr. Dobbs Journal Archives: ftp.uwp.edu:/pub/msdos/demos/source Distribution: grphprg.lzh ÖÄÄÄÄÄÄÄÄÄÄ´% VLA Presents: Intro to Assembler %ÃÄÄÄÄÄÄÄÄÄÄ· º º ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ ¯ Dedicated To Those Who Wish To Begin Exploring The Art Of Assembler. ® ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ  VLA Members Are  ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (© Draeden - Main Coder ª) (© The Priest - Coder/ Artist ª) (© Lithium - Coder/Ideas/Ray Tracing ª) (© The Kabal - Coder/Ideas/Artwork ª) (© Desolation - Artwork/Ideas ª) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Finn - Mods/Sounds ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ͵ Contact Us On These Boards ÆÍÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ· º º ³ % Phantasm BBS .................................. (206) 232-5912 ³ ³ * The Deep ...................................... (305) 888-7724 ³ ³ * Dark Tanget Systems ........................... (206) 722-7357 ³ ³ * Metro Holografix .............................. (619) 277-9016 ³ ³ ³ º % - World Head Quarters * - Distribution Site º ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ Or Via Internet Mail For The Group: tkabal@carson.u.washington.edu Or to reach the other members: - draeden@u.washington.edu - - lithium@u.washington.edu - - desolation@u.washington.edu- ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ VLA 3/93 Introduction to ASSEMBLER ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here's something to help those of you who were having trouble understanding the instructional programs we released. Dreaden made these files for the Kabal and myself when we were just learning. These files go over some of the basic concepts of assembler. Bonus of bonuses. These files also have programs imbedded in them. Most of them have a ton of comments so even the beginning programmers should be able to figure them out. If you'd like to learn more, post a message on Phantasm. We need to know where you're interests are before we can make more files to bring out the little programmers that are hiding inside all of us. Lithium/VLA ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ First thing ya need to know is a little jargon so you can talk about the basic data structures with your friends and neighbors. They are (in order of increasing size) BIT, NIBBLE, BYTE, WORD, DWORD, FWORD, PWORD and QWORD, PARA, KiloByte, MegaByte. The ones that you'll need to memorize are BYTE, WORD, DWORD, KiloByte, and MegaByte. The others aren't used all that much, and you wont need to know them to get started. Here's a little graphical representation of a few of those data structures: (The zeros in between the || is a graphical representation of the number of bits in that data structure.) ÄÄÄÄÄÄ 1 BIT : |0| The simplest piece of data that exists. Its either a 1 or a zero. Put a string of them together and you have a BASE-2 number system. Meaning that instead of each 'decimal' place being worth 10, its only worth 2. For instance: 00000001 = 1; 00000010 = 2; 00000011 = 3, etc.. ÄÄÄÄÄÄ 1 NIBBLE: |0000| 4 BITs The NIBBLE is half a BYTE or four BITS. Note that it has a maximum value of 15 (1111 = 15). Not by coincidence, HEXADECIMAL, a base 16 number system (computers are based on this number system) also has a maximum value of 15, which is represented by the letter 'F'. The 'digits' in HEXADECIMAL are (in increasing order): "0123456789ABCDEF" The standard notation for HEXADECIMAL is a zero followed by the number in HEX followed by a lowercase "h" For instance: "0FFh" = 255 DECIMAL. ÄÄÄÄÄÄ 1 BYTE |00000000| 2 NIBBLEs ÀÄ AL ÄÙ 8 BITs The BYTE is the standard chunk of information. If you asked how much memory a machine had, you'd get a response stating the number of BYTEs it had. (Usually preceded by a 'Mega' prefix). The BYTE is 8 BITs or 2 NIBBLEs. A BYTE has a maximum value of 0FFh (= 255 DECIMAL). Notice that because a BYTE is just 2 NIBBLES, the HEXADECIMAL representation is simply two HEX digits in a row (ie. 013h, 020h, 0AEh, etc..) The BYTE is also that size of the 'BYTE sized' registers - AL, AH, BL, BH, CL, CH, DL, DH. ÄÄÄÄÄÄ 1 WORD |0000000000000000| 2 BYTEs ÀÄ AH ÄÙÀÄ AL ÄÙ 4 NIBBLEs ÀÄÄÄÄÄ AX ÄÄÄÄÄÙ 16 BITs The WORD is just two BYTEs that are stuck together. A word has a maximum value of 0FFFFh (= 65,535). Since a WORD is 4 NIBBLEs, it is represented by 4 HEX digits. This is the size of the 16bit registers on the 80x86 chips. The registers are: AX, BX, CX, DX, DI, SI, BP, SP, CS, DS, ES, SS, and IP. Note that you cannot directly change the contents of IP or CS in any way. They can only be changed by JMP, CALL, or RET. ÄÄÄÄÄÄ 1 DWORD 2 WORDs |00000000000000000000000000000000| 4 BYTEs ³ ÀÄ AH ÄÙÀÄ AL ÄÙ 8 NIBBLEs ³ ÀÄÄÄÄÄ AX ÄÄÄÄÄÙ 32 BITs ÀÄÄÄÄÄÄÄÄÄÄÄÄ EAX ÄÄÄÄÄÄÄÄÄÄÄÄÄÙ A DWORD (or "DOUBLE WORD") is just two WORDs, hence the name DOUBLE-WORD. This can have a maximum value of 0FFFFFFFFh (8 NIBBLEs, 8 'F's) which equals 4,294,967,295. Damn large. This is also the size or the 386's 32bit registers: EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP, EIP. The 'E ' denotes that they are EXTENDED registers. The lower 16bits is where the normal 16bit register of the same name is located. (See diagram.) ÄÄÄÄÄÄ 1 KILOBYTE |-lots of zeros (8192 of 'em)-| 256 DWORDs 512 WORDs 1024 BYTEs 2048 NIBBLEs 8192 BITs We've all heard the term KILOBYTE byte, before, so I'll just point out that a KILOBYTE, despite its name, is -NOT- 1000 BYTEs. It is actually 1024 bytes. ÄÄÄÄÄÄ 1 MEGABYTE |-even more zeros (8,388,608 of 'em)-| 1,024 KILOBYTEs 262,144 DWORDs 524,288 WORDs 1,048,576 BYTEs 2,097,152 NIBBLEs 8,388,608 BITs Just like the KILOBYTE, the MEGABYTE is -NOT- 1 million bytes. It is actually 1024*1024 BYTEs, or 1,048,578 BYTEs ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Now that we know what the different data types are, we will investigate an annoying little aspect of the 80x86 processors. I'm talking about nothing other than SEGMENTS & OFFSETS! SEGMENTS & OFFSETS: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Pay close attention, because this topic is (I believe) the single most difficult (or annoying, once you understand it) aspect of ASSEMBLER. An OverView: The original designers of the 8088, way back when dinasaurs ruled the planet, decided that no one would ever possibly need more than one MEG (short for MEGABYTE :) of memory. So they built the machine so that it couldn't access above 1 MEG. To access the whole MEG, 20 BITs are needed. Problem was that the registers only had 16 bits, and if the used two registers, that would be 32 bits, which was way too much (they thought.) So they came up with a rather brilliant (blah) way to do their addressing- they would use two registers. They decided that they would not be 32bits, but the two registers would create 20 bit addressing. And thus Segments and OFfsets were born. And now the confusing specifics. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ OFFSET = SEGMENT*16 SEGMENT = OFFSET /16 ;note that the lower 4 bits are lost SEGMENT * 16 |0010010000010000----| range (0 to 65535) * 16 + OFFSET |----0100100000100010| range (0 to 65535) = 20 bit address |00101000100100100010| range 0 to 1048575 (1 MEG) ÀÄÄÄÄÄ DS ÄÄÄÄÄÙ ÀÄÄÄÄÄ SI ÄÄÄÄÄÙ ÀÄ OverlapÄÙ This shows how DS:SI is used to construct a 20 bit address. Segment registers are: CS, DS, ES, SS. On the 386+ there are also FS & GS Offset registers are: BX, DI, SI, BP, SP, IP. In 386+ protected mode, ANY general register (not a segment register) can be used as an Offset register. (Except IP, which you can't access.) CS:IP => Points to the currently executing code. SS:SP => Points to the current stack position. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ If you'll notice, the value in the SEGMENT register is multiplied by 16 (or shifted left 4 bits) and then added to the OFFSET register. Together they create a 20 bit address. Also Note that there are MANY combinations of the SEGMENT and OFFSET registers that will produce the same address. The standard notation for a SEGment/OFFset pair is: ÄÄÄÄ SEGMENT:OFFSET or A000:0000 ( which is, of course in HEX ) Where SEGMENT = 0A000h and OFFSET = 00000h. (This happens to be the address of the upper left pixel on a 320x200x256 screen.) ÄÄÄÄ You may be wondering what would happen if you were to have a segment value of 0FFFFh and an offset value of 0FFFFh. Take notice: 0FFFFh * 16 (or 0FFFF0h ) + 0FFFFh = 1,114,095, which is definately larger than 1 MEG (which is 1,048,576.) This means that you can actually access MORE than 1 meg of memory! Well, to actually use that extra bit of memory, you would have to enable something called the A20 line, which just enables the 21st bit for addressing. This little extra bit of memory is usually called "HIGH MEMORY" and is used when you load something into high memory or say DOS = HIGH in your AUTOEXEC.BAT file. (HIMEM.SYS actually puts it up there..) You don't need to know that last bit, but hey, knowledge is good, right? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ THE REGISTERS: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I've mentioned AX, AL, and AH before, and you're probably wondering what exactly they are. Well, I'm gonna go through one by one and explain what each register is and what it's most common uses are. Here goes: ÄÄÄÄ AX (AH/AL): AX is a 16 bit register which, as metioned before, is merely two bytes attached together. Well, for AX, BX, CX, & DX you can independantly access each part of the 16 bit register through the 8bit (or byte sized) registers. For AX, they are AL and AH, which are the Low and High parts of AX, respectivly. It should be noted that any change to AL or AH, will change AX. Similairly any changes to AX may or may not change AL and AH. For instance: ÄÄÄÄÄÄÄÄ Let's suppose that AX = 00000h (AH and AL both = 0, too) mov AX,0 mov AL,0 mov AH,0 Now we set AL = 0FFh. mov AL,0FFh :AX => 000FFh ;I'm just showing ya what's in the registers :AL => 0FFh :AH => 000h Now we increase AX by one: INC AX :AX => 00100h (= 256.. 255+1= 256) :AL => 000h (Notice that the change to AX changed AL and AH) :AH => 001h Now we set AH = 0ABh (=171) mov AH,0ABh :AX => 0AB00h :AL => 000h :AH => 0ABh Notice that the first example was just redundant... We could've set AX = 0 by just doing mov ax,0 :AX => 00000h :AL => 000h :AH => 000h I think ya got the idea... ÄÄÄÄÄÄÄÄ SPECIAL USES OF AX: Used as the destination of an IN (in port) ex: IN AL,DX IN AX,DX Source for the output for an OUT ex: OUT DX,AL OUT DX,AX Destination for LODS (grabs byte/word from [DS:SI] and INCreses SI) ex: lodsb (same as: mov al,[ds:si] ; inc si ) lodsw (same as: mov ax,[ds:si] ; inc si ; inc si ) Source for STOS (puts AX/AL into [ES:DI] and INCreses DI) ex: stosb (same as: mov [es:di],al ; inc di ) stosw (same as: mov [es:di],ax ; inc di ; inc di ) Used for MUL, IMUL, DIV, IDIV ÄÄÄÄ BX (BH/BL): same as AX (BH/BL) SPECIAL USES: As mentioned before, BX can be used as an OFFSET register. ex: mov ax,[ds:bx] (grabs the WORD at the address created by DS and BX) CX (CH/CL): Same as AX SPECIAL USES: Used in REP prefix to repeat an instruction CX number of times ex: mov cx,10 mov ax,0 rep stosb ;this would write 10 zeros to [ES:DI] and increase ;DI by 10. Used in LOOP ex: mov cx,100 THELABEL: ;do something that would print out 'HI' loop THELABEL ;this would print out 'HI' 100 times ;the loop is the same as: dec cx jne THELABAL DX (DH/DL): Same as above SPECIAL USES: USED in word sized MUL, DIV, IMUL, IDIV as DEST for high word or remainder ex: mov bx,10 mov ax,5 mul bx ;this multiplies BX by AX and puts the result ;in DX:AX ex: (continue from above) div bx ;this divides DX:AX by BX and put the result in AX and ;the remainder (in this case zero) in DX Used as address holder for IN's, and OUT's (see ax's examples) INDEX REGISTERS: DI: Used as destination address holder for stos, movs (see ax's examples) Also can be used as an OFFSET register SI: Used as source address holder for lods, movs (see ax's examples) Also can be used as OFFSET register Example of MOVS: movsb ;moves whats at [DS:SI] into [ES:DI] and increases movsw ; DI and SI by one for movsb and 2 for movsw NOTE: Up to here we have assumed that the DIRECTION flag was cleared. If the direction flag was set, the DI & SI would be DECREASED instead of INCREASED. ex: cld ;clears direction flag std ;sets direction flag STACK RELATED INDEX REGISTERS: BP: Base Pointer. Can be used to access the stack. Default segment is SS. Can be used to access data in other segments throught the use of a SEGMENT OVERRIDE. ex: mov al,[ES:BP] ;moves a byte from segment ES, offset BP Segment overrides are used to specify WHICH of the 4 (or 6 on the 386) segment registers to use. SP: Stack Pointer. Does just that. Segment overrides don't work on this guy. Points to the current position in the stack. Don't alter unless you REALLY know what you are doing. SEGMENT REGISTERS: DS: Data segment- all data read are from the segment pointed to be this segment register unless a segment overide is used. Used as source segment for movs, lods This segment also can be thought of as the "Default Segment" because if no segment override is present, DS is assumed to be the segmnet you want to grab the data from. ES: Extra Segment- this segment is used as the destination segment for movs, stos Can be used as just another segment... You need to specify [ES:°°] to use this segment. FS: (386+) No particular reason for it's name... I mean, we have CS, DS, and ES, why not make the next one FS? :) Just another segment.. GS: (386+) Same as FS OTHERS THAT YOU SHOULDN'T OR CAN'T CHANGE: CS: Segment that points to the next instruction- can't change directly IP: Offset pointer to the next instruction- can't even access The only was to change CS or IP would be through a JMP, CALL, or RET SS: Stack segment- don't mess with it unless you know what you're doing. Changing this will probably crash the computer. This is the segment that the STACK resides in. ÄÄÄÄÄÄÄÄ Heck, as long as I've mentioned it, lets look at the STACK: The STACK is an area of memory that has the properties of a STACK of plates- the last one you put on is the first one take off. The only difference is that the stack of plates is on the roof. (Ok, so that can't really happen... unless gravity was shut down...) Meaning that as you put another plate (or piece of data) on the stack, the STACK grows DOWNWARD. Meaning that the stack pointer is DECREASED after each PUSH, and INCREASED after each POP. _____ Top of the allocated memory in the stack segment (SS) þ þ þ þ ® SP (the stack pointer points to the most recently pushed byte) Truthfully, you don't need to know much more than a stack is Last In, First Out (LIFO). WRONG ex: push cx ;this swaps the contents of CX and AX push ax ;of course, if you wanted to do this quicker, you'd ... pop cx ;just say XCHG cx,ax pop ax ; but thats not my point. RIGHT ex: push cx ;this correctly restores AX & CX push ax ... pop ax pop cx ÄÄÄÄÄÄÄÄÄÄÄÄ Now I'll do a quick run through on the assembler instructions that you MUST know: ÄÄÄÄ MOV: Examples of different addressing modes: MOV ax,5 ;moves and IMMEDIATE value into ax (think 'AX = 5') MOV bx,cx ;moves a register into another register MOV cx,[SI] ;moves [DS:SI] into cx (the Default Segment is Used) MOV [DI+5],ax ;moves ax into [DS:DI+5] MOV [ES:DI+BX+34],al ;same as above, but has a more complicated ;OFFSET (=DI+BX+34) and a SEGMENT OVERRIDE MOV ax,[546] ;moves whats at [DS:546] into AX Note that the last example would be totally different if the brackets were left out. It would mean that an IMMEDIATE value of 546 is put into AX, instead of what's at offset 546 in the Default Segment. ANOTHER STANDARD NOTATION TO KNOW: Whenever you see brackets [] around something, it means that it refers to what is AT that offset. For instance, say you had this situation: ÄÄÄÄÄÄÄÄÄÄÄÄ MyData dw 55 ... mov ax,MyData ÄÄÄÄÄÄÄÄÄÄÄÄ What is that supposed to mean? Is MyData an Immediate Value? This is confusing and for our purposes WRONG. The 'Correct' way to do this would be: ÄÄÄÄÄÄÄÄÄÄÄÄ MyData dw 55 ... mov ax,[MyData] ÄÄÄÄÄÄÄÄÄÄÄÄ This is clearly moving what is AT the address of MyData, which would be 55, and not moving the OFFSET of MyData itself. But what if you actually wanted the OFFSET? Well, you must specify directly. ÄÄÄÄÄÄÄÄÄÄÄÄ MyData dw 55 ... mov ax,OFFSET MyData ÄÄÄÄÄÄÄÄÄÄÄÄ Similiarly, if you wanted the SEGMENT that MyData was in, you'd do this: ÄÄÄÄÄÄÄÄÄÄÄÄ MyData dw 55 ... mov ax,SEG MyData ÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ INT: Examples: INT 21h ;calls DOS standard interrupt # 21h INT 10h ;the Video BIOS interrupt.. INT is used to call a subroutine that performs some function that you'd rather not write yourself. For instance, you would use a DOS interrupt to OPEN a file. You would similiarly use the Video BIOS interrupt to set the screen mode, move the cursor, or to do any other function that would be difficult to program. Which subroutine the interrupt preforms is USUALLY specified by AH. For instance, if you wanted to print a message to the screen you'd use INT 21h, subfunction 9 by doing this: ÄÄÄÄÄÄÄÄÄÄÄÄ mov ah,9 int 21h ÄÄÄÄÄÄÄÄÄÄÄÄ Yes, it's that easy. Of course, for that function to do anything, you need to specify WHAT to print. That function requires that you have DS:DX be a FAR pointer that points to the string to display. This string must terminate with a dollar sign. Here's an example: ÄÄÄÄÄÄÄÄÄÄÄÄ MyMessage db "This is a message!$" ... mov dx,OFFSET MyMessage mov ax,SEG MyMessage mov ds,ax mov ah,9 int 21h ... ÄÄÄÄÄÄÄÄÄÄÄÄ The DB, like the DW (and DD) merely declares the type of a piece of data. DB => Declare Byte (I think of it as 'Data Byte') DW => Declare Word DD => Declare Dword Also, you may have noticed that I first put the segment value into AX and then put it into DS. I did that because the 80x86 does NOT allow you to put an immediate value into a segment register. You can, however, pop stuff into a Segment register or mov an indexed value into the segment register. A few examples: ÄÄÄÄÄÄÄÄÄÄÄÄ LEGAL: mov ax,SEG MyMessage mov ds,ax push SEG Message pop ds mov ds,[SegOfMyMessage] ;where [SegOfMyMessage] has already been loaded with ; the SEGMENT that MyMessage resides in ILLEGAL: mov ds,10 mov ds,SEG MyMessage ÄÄÄÄÄÄÄÄÄÄÄÄ Well, that's about it for what you need to know to get started... ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ And now the FRAME for an ASSEMBLER program. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Basic Frame for an Assembler program using Turbo Assembler simplified directives is: ;===========- DOSSEG ;This arranges the segments in order according DOS standards ;CODE, DATA, STACK .MODEL SMALL ;dont worry about this yet .STACK 200h ;tells the compiler to put in a 200h byte stack .CODE ;starts code segment ASSUME CS:@CODE, DS:@CODE START: ;generally a good name to use as an entry point mov ax,4c00h int 21h END START ;===========- By the way, a semicolon means the start of a comment. If you were to enter this program and TASM & TLINK it, it would execute perfectly. It will do absolutly nothing, but it will do it well. What it does: Upon execution, it will jump to START. move 4c00h into AX, and call the DOS interrupt, which exits back to DOS. Outout seen: NONE ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ That's nice, eh? If you've understood the majority of what was presented in this document, you are ready to start programming! See ASM0.TXT and ASM0.ASM to continue this wonderful assembler stuff... Written By Draeden/VLA Text for ASM #0 Hello there, this is Draeden typing this wonderful document. This is an explanation of the basic assembler frame. This document assumes that you know what hexdecimal is and somewhat how it works, that you have a copy of TASM and TLINK, that you know what AX is, and how it relates to AL and AH, and you know the commands: MOV xx,xx JMP xxxx and INT xx I'm also making the rash assumption that you want to learn ASSEMBLER. :) To assemble ASM0.ASM into an executable do the following: TASM ASM0 TLINK ASM0 Now you can exececute this wonderful program. Go ahead. Try it. In case you are having problems figuring out how to execute this, just type: ASM0 (followed by the enter key) No, you did nothing wrong. This code (ASM0.ASM) does nothing. All it does is return control to DOS. It is the basic frame for an assembler program. All of the programs that I write use this frame. If you want to know what each part does, read on. If you already know, just go read ASM1.TXT. The number followed by the colon means that this is from ASM0.ASM and tells which line it is from. 1: DOSSEG DOSSEG Sorts the segment using DOS standard, which is: 1) 'code' segments (in alphabetical order) 2) 'data' segments (in alphabetical order) 3) 'stack' segments (again, in alphabetical order) Although it may not seem clear what this does, don't worry about it. Just have it as the first line in your assembler programs, until you understand it. 2: .MODEL SMALL MODEL ONLY needs to be used if you use the simplified segments, which I strongly recommend. In a nutshell, .MODEL Selects the MODEL to use. This is used so that this code can be linked with C, PASCAL, ADA, BASIC, other ASSEMBLER program, and other languages with ease. It also tells the compiler how to treat your code and data segments. NEAR means that the data/code can be reached using a 16bit pointer (offset) FAR means that a SEGMENT:OFFSET pair must be used to access all the data/code Possible MODELS are: TINY: Code and Data must fit in same 64k segment. Both Code and Data are NEAR. SMALL: Code & Data have seperate segment, but must be each less than 64k Both Code and Data are NEAR. For most applications, this will suffice. MEDIUM: Code may be larger than 64k, but Data has to be less than 64k Code is FAR, Data is NEAR. COMPACT: Code is less than 64k, but Data may be greater than 64k Code is NEAR, Data is FAR. LARGE: Both Code & Data can be greather than 64k. Both are FAR, but a single array cannot be greater than 64k. Note that max array size means nothing if you are just writing in assembler. This only matters when you link to C or another high level language. HUGE: Same as LARGE, but arrays can be greater than 64k. What that means is that the array index is a far pointer, instead of a NEAR one. LARGE and HUGE are identicle to the assembler programmer. 3: .STACK 200h Tells the compiler to set up a 200h byte stack upon execution of the program. NOTE: the size you choose for the stack does not change the size of the file on disk. You can see what I mean by changing the 200h to, say, 400h and then recompiling. The file sizes are identicle. This could be replaced with: : MyStack SEGMENT PARA PUBLIC STACK 'STACK' : db 200h dup (0) : MyStack ENDS BUT, doing it this way makes your executable 512 bytes bigger. If you were to double to 400h, the executable would be another 512 bytes bigger. I think it's pretty obvious why the simplified version is preferred. 4: .DATA Simplified, unnamed 'data' segment. This is where those simplified segments become very handy. If you were to write out the segment declaration the regular way, you'd have to write something like this: : MyData SEGMENT PARA PUBLIC 'DATA' : : ... ;your data goes here... : : MyData ENDS Where 'MyData' is the name of the segment, public means that its, well, public, and PARA is the alignment of the start of the segment. 'DATA' specifies the type of the segment. Instead of PARA, WORD or BYTE could have been used. (PARA = segment will start on an adress that is a multiple of 16, WORD = even addresses, BYTE = where ever it lands.) 5: .CODE Pretty much the same story as above, but this is for the code segment. Could be replaced with: - IN MASM MODE - : MyCode SEGMENT PARA PUBLIC 'CODE' : ... : MyCode ENDS - IN IDEAL MODE - : SEGMENT MyCode PARA PUBLIC 'CODE' : ... : ENDS MyCode ;the 'MyCode' is optional in IDEAL mode 6: START: This is just a label. Labels just provide a way of refencing memory easily. Like I could say "JMP START" which would jump to the label START and execute the code immediatly after it. Or I could say MOV AX,[Start], which would grab the WORD that was immediatly after the label START. 7: mov ax,4c00h 8: int 21h This bit of code calls DOS function # 4ch, which returns control to DOS and sends back the error level code that is in AL (which is zero). Note that for all int 21h DOS functions, AH contains the function number. THIS MUST BE AT THE END OF THE CODE! If it isn't, the code will continue to run... right out of the end of your program and will execute whatever code is there! The program will crash with out it! 9: END START This tells the compiler that we are all done with our program and that it can stop compiling, now. And it tells the compiler to put the entry point at the label START. This means that DOS is effectivly starting your program by executing this: : JMP START As you would probably guess, if you just put `END' instead of `END START' and you compiled and linked the program, when you went to execute the code, the computer will probably freeze because it does not know where to start execution. Ok, now that you know what the frame is/does, lets actually make the program do something. Lets be wild and crazy, and PRINT A MESSAGE! CONTINUED IN ASM1.TXT ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ASM0.ASM ³ ÀÄÄÄÄÄÄÄÄÄÄÙ DOSSEG .MODEL SMALL .STACK 200h .DATA .CODE START: ; ; Your code goes here... ; mov ax,4c00h int 21h END START ; THIS CODE DOES ABSOLUTLY NOTHING EXCEPT RETURN CONTROL TO DOS! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ASM1.ASM - print a string ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, here's the classic example for the first program in just about every language. It prints a message to the screen by using a DOS function. More specifically, it uses function 9 of interrupt 21h. Here's the mock specification for the function: þ- |IN: ah = 9 ;ah tells INT 21h which function you want | DS:DX = FAR pointer to the string to be printed. | ;the string must terminate with a dollar sign ($) | |OUT: Prints the string to the screen þ- Other than that function, there's nothing new that can't easily be figured out. The directive SEG, as you might have guessed, returns the segment that the specified label is in. OFFSET returns the offset from the begining of the segment to the specified label. Together, you can form a FAR pointer to a specified label. Another thing you might wonder about is why I put the SEG Message into AX and THEN Put it in DS. The answer is: You have to. An immediate value cannot be put into a segment register, but a register or an indexed value can. For instance: These are legal: : mov DS,AX : mov DS,[TheSegment] But these are not: : mov DS,50 : mov DS,0a000h One last piece of info: in the lines: :Message db "This was printed using function 9 " : db "of the DOS interrupt 21h.$" The DB is just a data type. Its the same as a CHAR in C, which is 1 byte in length. Other common data types are: DW same as an INT in C - 2 bytes DD same as a double int or long int or a FAR pointer - 4 bytes Well, that's pretty much it for this short section... Try playing around with the 'print' function... Ya learn best by playing with it. One last side note: I COULD have put the message in the CODE segment instead, by doing this: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ DOSSEG .MODEL SMALL .STACK 200h .CODE Message db "Hey look! I'm in the code segment!$" START: mov ax,cs ;since CS already points to the same segment as Message, mov ds,ax ;I don't have to explicitly load the segment that message ;is in.. mov dx,offset Message mov ah,9 int 21h mov ax,4c00h ;Returns control to DOS int 21h ;MUST be here! Program will crash without it! END START ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The advantage to having all your data in the CODE segment is that DS and ES can be pointing anywhere and you can still access your data via a segment override! Example: say I'm in the middle of copying one section of the screen memory to another and I need to access the variable "NumLines" I'd do it like this: ÄÄÄÄÄÄÄÄ mov ax,[CS:NumLines] ;this is in IDEAL mode ^^^ ÄÄÄÄÄÄÄÄ Code Segment override Pretty flexable, eh? ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ASM1.ASM ³ ÀÄÄÄÄÄÄÄÄÄÄÙ DOSSEG .MODEL SMALL .STACK 200h .DATA Message db "This was printed using function 9 " db "of the DOS interrupt 21h.$" .CODE START: mov ax,seg Message ;moves the SEGMENT that `Message' is in into AX mov ds,ax ;moves ax into ds (ds=ax) ;you cannot do this -> mov ds,seg Message mov dx,offset Message ;move the OFFSET of `Message' into DX mov ah,9 ;Function 9 of DOS interupt 21h prints a string that int 21h ;terminates with a "$". It requires a FAR pointer to ;what is to be printed in DS:DX mov ax,4c00h ;Returns control to DOS int 21h ;MUST be here! Program will crash without it! END START ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ASM2.TXT - intro to keyboard and flow control ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Alright. This bit of code introduces control flow, keyboard input, and a way to easily print out one character. ÄÄÄÄÄÄÄÄ First off, lets examine the easiest one: printing a character. It's like this: you put the character to print in DL, put 2 in AH and call interrupt 21h. Damn easy. ÄÄÄÄÄÄÄÄ Ok, lets look at the next easiest one: keyboard input. There are quite a few functions related to INT 16h (the keyboard interrupt.) They are: FUNCTION# --------- 0h -Gets a key from the keyboard buffer. If there isn't one, it waits until there is. Returns the SCAN code in ah, and the ASCII translation in AL 1h -Checks to see if a key is ready to grab. Sets the zero flag if a key is ready to grab. Grab it with Fn# 0 This also returns the same info about the key as Fn#0, but does not remove it from the buffer. 2h -Returns the shift flags in al. They are: bit 7 - Insert active bit 6 - Caps lock active bit 5 - Num Lock active bit 4 - Scroll lock active bit 3 - Alt pressed bit 2 - Ctrl pressed bit 1 - Left shift pressed bit 0 - right shift pressed 3h -You can set the Typematic Rate and delay with this function registers must be set as follows AL = 5 BH = Delay value (0-3: 250,500,750,1000 millisec) BL = Typematic rate (0-1fh) 1fh = slowest (2 chars per sec) 0 =fastest (30 chars per second) 4h -Key Click control - not important 5h -STUFF the keyboard input: CH = scan code CL = ascii code output: al = 0 no error al = 1 keyboard buffer is full 10h -Same as #0, but its for the extended keyboard. Checks all the keys. 11h -Same as #1, but for the extended keyboard. 12h -Same as #2, but AH contains additional shift flags: bit 7 - Sys req pressed bit 6 - Caps lock active bit 5 - Num Lock active bit 4 - Scroll lock active bit 3 - Right Alt active bit 2 - Right Ctrl active bit 1 - Left Alt active bit 0 - Right Alt active Al is EXACTLY the same as in Fn#2 WHERE AH= the function number when you call INT 16h ÄÄÄÄÄÄÄÄ That's neat-o, eh? Now on to flow controll via CMP and Jcc... CMP: ÄÄÄ CMP is the same as SUB, but it does NOT alter any registers, only the flags. This is used in conjunction with Jcc. Jcc: ÄÄÄ Ok, Jcc is not a real instruction, it means 'jump if conditionis met.' I'll break this into 3 sections, comparing signed numbers, comparing unsigned numbers, and misc. Note that a number being 'unsigned' or 'signed' only depends on how you treat it. That's why there are different Jcc for each... If you treat it as a signed number, the highest bit denotes whether it's negative or not. Prove to yourself that 0FFFFh is actually -1 by adding 1 to 0FFFFh. You should get a big zero: 00000h. (Remember that the number is ONLY 16 bits and the carry dissapears..) UNSIGNED: ÄÄÄÄÄÄÄÄ JA -jumps if the first number was above the second number JAE -same as above, but will also jump if they are equal JB -jumps if the first number was below the second JBE -duh... JNA -jumps if the first number was NOT above... (same as JBE) JNAE-jumps if the first number was NOT above or the same as.. (same as JB) JNB -jumps if the first number was NOT below... (same as JAE) JNBE-jumps if the first number was NOT below or the same as.. (same as JA) JZ -jumps if the two numbers were equal (zero flag = 1) JE -same as JZ, just a different name JNZ -pretty obvious, I hope... JNE -same as above... SIGNED: ÄÄÄÄÄÄ JG -jumps if the first number was > the second number JGE -same as above, but will also jump if they are equal JL -jumps if the first number was < the second JLE -duh... JNG -jumps if the first number was NOT >... (same as JLE) JNGE-jumps if the first number was NOT >=.. (same as JL) JNL -jumps if the first number was NOT <... (same as JGE) JNLE-jumps if the first number was NOT <=... (same as JG) JZ, JE, JNZ, JNE - Same as for Unsigned MISC: ÄÄÄÄ JC -jumps if the carry flag is set JNC -Go figgure... Here's the rest of them... I've never had to use these, though... JO -jump if overflow flag is set JNO -... JP -jump is parity flag is set JNP -... JPE -jump if parity even (same as JP) JPO -jump if parity odd (same as JNP) JS -jumps if sign flag is set JNS -... Here's the flags really quickly: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ bit# 8 7 6 5 4 3 2 1 0 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ symbol: O D I T S Z A P C O = OverFlow flag D = Direction flag * I = Interrupt flag T = Trap flag S = Sign flag Z = Zero flag * A = Auxiliary flag C = Carry flag * The * denotes the ones that you should know. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ That's it for now... Until next time... Draeden\VLA ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ASM2.ASM ³ ÀÄÄÄÄÄÄÄÄÄÄÙ ; ASM2.ASM ; Prints messages get keyboard input and has control flow DOSSEG .MODEL SMALL .STACK 200h .DATA Prompt db 13,10,"Do you want to be prompted again? (Y/N) $" NoMessage db 13,10,"Ok, then I won't prompt you anymore.$" YesMessage db 13,10,"Here comes another prompt!$" UnKnownKey db 13,10,"Please hit either Y or N.$" .CODE START: mov ax,@DATA ;moves the segment of data into ax mov ds,ax MainLoop: mov ah,9 mov dx,offset Prompt int 21h ;print a message mov ah,0 int 16h ;get a key, returned in AX ;AL is the ASCII part ;AH is the SCAN CODE push ax mov dl,al mov ah,2 int 21h ;print character in dl pop ax cmp al,"Y" ;was the character a 'Y'? jne NotYes ;nope it was Not Equal mov ah,9 mov dx,offset YesMessage int 21h jmp MainLoop NotYes: cmp al,"N" ;was the character a 'N' je ByeBye ;Yes, it was Equal mov dx,offset UnknownKey mov ah,9 int 21h jmp MainLoop ByeBye: mov dx,offset NoMessage mov ah,9 int 21h mov ax,4c00h ;Returns control to DOS int 21h ;MUST be here! Program will crash without it! END START - ASMVLA01 - File I/O - 04/14/93 - Lately we have been quite busy with school, so this second issue is a little behind schedule. But that's life... This little issue will quickly show off the DOS file functions: read, write, open, close, create & others. They are all pretty much the same, so there isn't a whole lot to go over. But, as a bonus, I'm going to throw in a bit about how to do a subroutine. Let's do the subroutine stuff first. `Procedures' as they are called, are declared like this: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ PROC TheProcedure ... ;do whatever.. ret ;MUST have a RET statement! ENDP TheProcedure ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In the procedure, you can do basically anything you want, just at the end of it, you say ret. You can also specify how to call the PROC by putting a NEAR or FAR after the procedure name. This tells the compiler whether to change segment AND offset, or just offset when the procedure is called. Note that if you don't specify, it compiles into whatever the default is for the current .MODEL (small = near, large = far) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ PROC TheProc NEAR ... ret ;this compiles to `retn' (return near- pops offset off ENDP TheProc ; stack only) OR PROC TheProc FAR ... ret ;compiles to `retf' pops both offset & segment off stack ENDP TheProc ; pops offset first ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ That's basically all there is to that. Note that if you REALLY wanted to be tricky, you could do a far jump by doing this: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ push seg TheProc push offset TheProc retf ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This would "return" you to the beginning of the procedure "TheProc"... This code is just to illustrate a point. If you actually did something like this and compiled and executed it, it would bomb. Know why? What happens when it hits the `ret' in the PROC? Well it pops off the offset and puts it in IP and then pops the segment and puts it in CS. Who knows what was on the stack... will return to an unknown address and probably crash. (It DEFINATELY will not continue executing your code.) Of course, the only stack operations are PUSH and POP. All they do is push or pop off the stack a word sized or a Dword sized piece of data. NEVER under ANY circumstance try to push a byte sized piece of data! The results are unpredictable. Well, not really, but just don't do it, ok? There are also two commands that'll save you some time and code space: PUSHA and POPA (push all and Pop all) PUSHA pushes the general registers in this order: AX, CX, DX, BX, SP, BP, SI, DI POPA pops the general registers in this order: DI, SI, BP, (sp), BX, DX, CX, AX SP is different because popa does NOT restore the value of SP. It merely pops it off and throws it away. For the 386+, pushad and popad push and pop all extended registers in the same order. You don't need to memorize the order, because you don't need to know the order until you go and get tricky. (hint: the location of AX on the stack is [sp + 14] - useful if you want to change what AX returns, but you did a pusha cause you wanted to save all the registers (except AX) Then you'd do a popa, and AX= whatever value you put in there. ÄÄÄÄ Alright, now a slightly different topic: memory management Ok, this isn't true by-the-book memory management, but you need to know one thing: Upon execution of a program, DOS gives it ALL memory up to the address A000:0000. This happens to be the beginning of the VGA buffer... Another thing you must know is that, if you used DOSSEG at the top of your file, the segment is the last piece of your program. The size of the segment is derived from the little command `STACK 200h' or whatever the value was that you put up there. The 200h is the number of bytes in the stack. To get the number of paragraphs, you'd divide by 16. Here's an example of how I can get a pointer to the first valid available segment that I can use for data: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov ax,ss ;grab the stack segment add ax,200h/16 ;add the size of the stack 200h/16 = 20h ;AX now contains the value of the first available segment the you can ; use. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is very nice, because you can just plop your data right there and you have a 64k buffer you can use for anything you want. Ok, say you want to find out how much memory is available to use. This would be done like this: (no suprises, I hope.) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov ax,ss ;grab the stack segment add ax,200h/16 ;add the size of the stack 200h/16 = 20h mov bx,0A000h ;upper limit of the free memory sub bx,ax ;bx= # of paragraphs available ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Pretty darn simple. That's enough of the overhead that you must know to understand the included ANSI viewer (asm3.asm) Now to the FILE I/O stuff... Files can be opened, read from, written to, created, and closed. To open a file, all you need to do is give the DOS interrupt a name & path. All references to that file are done through what's known as a file handle. A file handle is simply a 16bit integer that DOS uses to identify the file. It's used more or less like an index into chart of pointers that point to a big structure that holds all the info about the file- like current position in the file, file type, etc.. all the data needed to maintain a file. The `FILES= 20' thing in your autoexec simply tells DOS how much memory to grab for those structures. ( Files=20 grabs enough room for 20 open files. ) ANYway, here's each of the important function calls and a rundown on what they do and how to work them. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE OPEN: Function 3Dh IN: ah= 3Dh al= open mode bits 7-3: Stuff that doesn't matter to us bits 2-0: Access code 000 read only access 001 write only access 010 read and write access DS:DX= pointer to the ASCIIZ filename ASCIIZ means that its an ASCII string with a Zero on the end. Returns: CF=1 error occured AX= error code- don't worry about what they are, if the carry is set, you didn't open the file. CF=0 no error AX= File Handle ;you need to keep this- it's your only way to ; reference your file! ÄÄÄÄ EXAMPLE ÄÄÄÄ [...] ;header stuff .CODE ;this stuff is used for all the examples FileName db "TextFile.TXT",0 FileHandle dw 0 Buffer db 300 dup (0) BytesRead dw 0 FileSize dd 0 [...] ;more stuff mov ax,3d00h ; open file for read only mov ax,cs mov ds,ax ;we use CS, cause it's pointing to the CODE segment ; and our file name is in the code segment mov dx,offset FileName int 21h jc FileError_Open mov [FileHandle],ax [...] ;etc... ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE CLOSE: Function 3Eh IN: AH= 3Eh BX= File Handle RETURN: CF=1 error occured, but who cares? ÄÄÄÄ EXAMPLE ÄÄÄÄ mov bx,[FileHandle] mov ah,3eh int 21h ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE READ: Function 3Fh IN: AH= 3Fh BX= File Handle CX= Number of bytes to read DS:DX= where to put data that is read from the file (in memory) RETURN: AX= number of bytes actually read- if 0, then you tried to read from the end of the file. ÄÄÄÄ EXAMPLE ÄÄÄÄ mov bx,[FileHandle] mov ax,cs mov ds,ax mov dx,offset buffer mov ah,3Fh mov cx,300 int 21h mov [BytesRead],ax ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE WRITE: Function 40h IN: AH= 40h BX= File Handle CX= Number of bytes to write DS:DX= where to read data from (in memory) to put on disk RETURN: AX= number of bytes actually written- if not equal to the number of bytes that you wanted to write, you have an error. ÄÄÄÄ EXAMPLE ÄÄÄÄ mov bx,[FileHandle] mov ax,cs mov ds,ax mov dx,offset buffer mov ah,40h mov cx,[BytesRead] int 21h cmp cx,ax jne FileError_Write ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE CREATE: Function 3Ch IN: ah= 3Ch cl= file attribute bit 0: read-only bit 1: hidden bit 2: system bit 3: volume label bit 4: sub directory bit 5: Archive bit 6&7: reserved DS:DX= pointer to the ASCIIZ filename ASCIIZ means that its an ASCII string with a Zero on the end. Returns: CF=1 error occured AX= error code- don't worry about what they are, if CF is set, you didn't create the file. CF=0 no error AX= File Handle ;you need to keep this- it's your only way to ; reference your file! ÄÄÄÄ EXAMPLE ÄÄÄÄ mov ah,3ch mov ax,cs mov ds,ax ;we use CS, cause it's pointing to the CODE segment ; and our file name is in the code segment mov dx,offset FileName mov cx,0 ;no attributes int 21h jc FileError_Create mov [FileHandle],ax ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE DELETE: Function 41h IN: ah= 41h DS:DX= pointer to the ASCIIZ filename Returns: CF=1 error occured AX= error code- 2= file not found, 3= path not found 5= access denied CF=0 no error ÄÄÄÄ EXAMPLE ÄÄÄÄ mov ah,41h ;kill the sucker mov ax,cs mov ds,ax mov dx,offset FileName int 21h jc FileError_Delete ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE MOVE POINTER: Function 42h IN: ah= 42h BX= File Handle CX:DX= 32 bit pointer to location in file to move to AL= 0 offset from beginning of file = 1 offset from curent position = 2 offset from the end of the file Returns: CF=1 error occured AX= error code- no move occured CF=0 no error DX:AX 32 bit pointer to indicate current location in file ÄÄÄÄ EXAMPLE ÄÄÄÄ mov ah,42h ;find out the size of the file mov bx,[FileHandle] xor cx,cx xor dx,dx mov al,2 int 21h mov [word low FileSize],ax mov [word high FileSize],dx ;load data into filesize (or in MASM mode, mov word ptr [FileSize],ax mov word ptr [FileSize+2],dx need I say why I like Ideal mode? ) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE CHANGE MODE: Function 43h IN: ah= 43h DS:DX= pointer to the ASCIIZ filename al= 0 returns file attributes in CX al= 1 sets file attributes to what's in CX Returns: CF=1 error occured AX= error code- 2= file not found, 3= path not found. 5= access denied CF=0 no error ÄÄÄÄ EXAMPLE ÄÄÄÄ Lets erase a hidden file in your root directory... FileName db "C:\msdos.sys",0 [...] mov ah,43h ;change attribute to that of a normal file mov ax,cs mov ds,ax mov dx,offset FileName mov al,1 ;set to whats in CX mov cx,0 ;attribute = 0 int 21h mov ah,41h ;Nuke it with the delete command int 21h ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, that's all for now. I hope this info is enough for you to do some SERIOUS damage... :) I just don't want to see any 'bombs' running around erasing the hidden files in the root directory, ok? Anyway, go take a look at asm3.asm- it's a SIMPLE ansi/text displayer. It just opens the file, reads it all into a "buffer" that was "allocated" immediatly after the stack & reads in the entire file (if it's < 64k) and prints out the file character by character via DOS's print char (fn# 2). Very simple and very slow. You'd need a better print routine to go faster... The quickest display programs would decode the ANSI on its own... But that's kinda a chore... Oh, well. Enjoy. Draeden/VLA Suggested projects: 1) Write a program that will try to open a file, but if it does not find it, the program creates the file and fills it with a simple text message. 2) Write a program that will input your keystrokes and write them directly to a text file. 3) The write & read routines actually can be used for a file or device. Try to figure out what the FileHandle for the text screen is by writing to the device with various file handles. This same channel, when read from, takes it's data from the keyboard. Try to read data from the keyboard. Maybe read like 20 characters... CTRL-Z is the end of file marker. 4) Try to use a file as `virtual memory'- open it for read/write access and write stuff to it and then read it back again after moving the cursor position. ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ASM3.ASM ³ ÀÄÄÄÄÄÄÄÄÄÄÙ ; VERY, VERY simple ANSI/text viewer ; ; Coded by Draeden [VLA] ; DOSSEG .MODEL SMALL .STACK 200h .CODE Ideal ;===- Data -=== BufferSeg dw 0 ErrMsgOpen db "Error opening `" FileName db "ANSI.TXT",0,8,"'$" ;8 is a delete character ;0 is required for filename ;(displays a space) FileLength dw 0 ;===- Subroutines -=== PROC DisplayFile NEAR push ds mov ax,cs mov ds,ax mov ax,3d00h ;open file (ah=3dh) mov dx,offset FileName int 21h jc OpenError mov bx,ax ;move the file handle into bx mov ds,[BufferSeg] mov dx,0 ;load to [BufferSeg]:0000 mov ah,3fh mov cx,0FFFFh ;try to read an entire segments worth int 21h mov [cs:FileLength],ax mov ah,3eh int 21h ;close the file cld mov si,0 mov cx,[cs:FileLength] PrintLoop: mov ah,2 lodsb mov dl,al int 21h ;print a character dec cx jne PrintLoop pop ds ret OpenError: mov ah,9 mov dx,offset ErrMsgOpen int 21h pop ds ret ENDP DisplayFile ;===- Main Program -=== START: mov ax,cs mov ds,ax mov bx,ss add bx,200h/10h ;get past the end of the file mov [BufferSeg],bx ;store the buffer segment call DisplayFile mov ax,4c00h int 21h END START Intel 8086 Family Architecture. . . . . . . . . . . . . . . . . . . . . 3 Instruction Clock Cycle Calculation . . . . . . . . . . . . . . . . . . 3 8088/8086 Effective Address (EA) Calculation . . . . . . . . . . . . . 3 Task State Calculation. . . . . . . . . . . . . . . . . . . . . . . . . 4 FLAGS - Intel 8086 Family Flags Register. . . . . . . . . . . . . . . . 4 MSW - Machine Status Word (286+ only) . . . . . . . . . . . . . . . . . 5 8086/80186/80286/80386/80486 Instruction Set. . . . . . . . . . . . . . 6 AAA - Ascii Adjust for Addition. . . . . . . . . . . . . . . . . . 6 AAD - Ascii Adjust for Division. . . . . . . . . . . . . . . . . . 6 AAM - Ascii Adjust for Multiplication. . . . . . . . . . . . . . . 6 AAS - Ascii Adjust for Subtraction . . . . . . . . . . . . . . . . 6 ADC - Add With Carry . . . . . . . . . . . . . . . . . . . . . . . 7 ADD - Arithmetic Addition. . . . . . . . . . . . . . . . . . . . . 7 AND - Logical And. . . . . . . . . . . . . . . . . . . . . . . . . 7 ARPL - Adjusted Requested Privilege Level of Selector (286+ PM). . 7 BOUND - Array Index Bound Check (80188+) . . . . . . . . . . . . . 8 BSF - Bit Scan Forward (386+). . . . . . . . . . . . . . . . . . . 8 BSR - Bit Scan Reverse (386+) . . . . . . . . . . . . . . . . . . 8 BSWAP - Byte Swap (486+) . . . . . . . . . . . . . . . . . . 8 BT - Bit Test (386+) . . . . . . . . . . . . . . . . . . 9 BTC - Bit Test with Compliment (386+). . . . . . . . . . . . . . . 9 BTR - Bit Test with Reset (386+) . . . . . . . . . . . . . . . . . 9 BTS - Bit Test and Set (386+) . . . . . . . . . . . . . . . . . . 9 CALL - Procedure Call. . . . . . . . . . . . . . . . . . . . . . . 10 CBW - Convert Byte to Word . . . . . . . . . . . . . . . . . . . . 10 CDQ - Convert Double to Quad (386+). . . . . . . . . . . . . . . . 10 CLC - Clear Carry. . . . . . . . . . . . . . . . . . . . . . . . . 11 CLD - Clear Direction Flag . . . . . . . . . . . . . . . . . . . . 11 CLI - Clear Interrupt Flag (disable) . . . . . . . . . . . . . . . 11 CLTS - Clear Task Switched Flag (286+ privileged). . . . . . . . . 11 CMC - Complement Carry Flag. . . . . . . . . . . . . . . . . . . . 11 CMP - Compare. . . . . . . . . . . . . . . . . . . . . . . . . . . 12 CMPS - Compare String (Byte, Word or Doubleword) . . . . . . . . . 12 CMPXCHG - Compare and Exchange . . . . . . . . . . . . . . . . . . 12 CWD - Convert Word to Doubleword . . . . . . . . . . . . . . . . . 12 CWDE - Convert Word to Extended Doubleword (386+). . . . . . . . . 13 DAA - Decimal Adjust for Addition. . . . . . . . . . . . . . . . . 13 DAS - Decimal Adjust for Subtraction . . . . . . . . . . . . . . . 13 DEC - Decrement. . . . . . . . . . . . . . . . . . . . . . . . . . 13 DIV - Divide . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 ENTER - Make Stack Frame (80188+) . . . . . . . . . . . . . . . . 14 ESC - Escape . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 HLT - Halt CPU . . . . . . . . . . . . . . . . . . . . . . . . . . 14 IDIV - Signed Integer Division . . . . . . . . . . . . . . . . . . 14 IMUL - Signed Multiply . . . . . . . . . . . . . . . . . . . . . . 15 IN - Input Byte or Word From Port. . . . . . . . . . . . . . . . . 15 INC - Increment. . . . . . . . . . . . . . . . . . . . . . . . . . 16 INS - Input String from Port (80188+) . . . . . . . . . . . . . . 16 INT - Interrupt. . . . . . . . . . . . . . . . . . . . . . . . . . 16 INTO - Interrupt on Overflow . . . . . . . . . . . . . . . . . . . 17 INVD - Invalidate Cache (486+). . . . . . . . . . . . . . . . . . 17 INVLPG - Invalidate Translation Look-Aside Buffer Entry (486+) . . 17 IRET/IRETD - Interrupt Return. . . . . . . . . . . . . . . . . . . 17 Jxx - Jump Instructions Table. . . . . . . . . . . . . . . . . . . 18 JCXZ/JECXZ - Jump if Register (E)CX is Zero. . . . . . . . . . . . 18 JMP - Unconditional Jump . . . . . . . . . . . . . . . . . . . . . 19 LAHF - Load Register AH From Flags . . . . . . . . . . . . . . . . 19 LAR - Load Access Rights (286+ protected). . . . . . . . . . . . . 19 LDS - Load Pointer Using DS. . . . . . . . . . . . . . . . . . . . 20 LEA - Load Effective Address . . . . . . . . . . . . . . . . . . . 20 LEAVE - Restore Stack for Procedure Exit (80188+). . . . . . . . . 20 LES - Load Pointer Using ES. . . . . . . . . . . . . . . . . . . . 20 LFS - Load Pointer Using FS (386+) . . . . . . . . . . . . . . . . 21 LGDT - Load Global Descriptor Table (286+ privileged). . . . . . . 21 LIDT - Load Interrupt Descriptor Table (286+ privileged) . . . . . 21 LGS - Load Pointer Using GS (386+) . . . . . . . . . . . . . . . . 21 LLDT - Load Local Descriptor Table (286+ privileged) . . . . . . . 22 LMSW - Load Machine Status Word (286+ privileged). . . . . . . . . 22 LOCK - Lock Bus. . . . . . . . . . . . . . . . . . . . . . . . . . 22 LODS - Load String (Byte, Word or Double). . . . . . . . . . . . . 22 LOOP - Decrement CX and Loop if CX Not Zero. . . . . . . . . . . . 23 LOOPE/LOOPZ - Loop While Equal / Loop While Zero . . . . . . . . . 23 LOOPNZ/LOOPNE - Loop While Not Zero / Loop While Not Equal . . . . 23 LSL - Load Segment Limit (286+ protected). . . . . . . . . . . . . 23 LSS - Load Pointer Using SS (386+) . . . . . . . . . . . . . . . . 24 LTR - Load Task Register (286+ privileged) . . . . . . . . . . . . 24 MOV - Move Byte or Word. . . . . . . . . . . . . . . . . . . . . . 24 MOVS - Move String (Byte or Word). . . . . . . . . . . . . . . . . 25 MOVSX - Move with Sign Extend (386+) . . . . . . . . . . . . . . . 25 MOVZX - Move with Zero Extend (386+) . . . . . . . . . . . . . . . 25 MUL - Unsigned Multiply. . . . . . . . . . . . . . . . . . . . . . 25 NEG - Two's Complement Negation. . . . . . . . . . . . . . . . . . 26 NOP - No Operation (90h) . . . . . . . . . . . . . . . . . . . . . 26 NOT - One's Compliment Negation (Logical NOT). . . . . . . . . . . 26 OR - Inclusive Logical OR. . . . . . . . . . . . . . . . . . . . . 26 OUT - Output Data to Port. . . . . . . . . . . . . . . . . . . . . 27 OUTS - Output String to Port (80188+) . . . . . . . . . . . . . . 27 POP - Pop Word off Stack . . . . . . . . . . . . . . . . . . . . . 27 POPA/POPAD - Pop All Registers onto Stack (80188+). . . . . . . . 28 POPF/POPFD - Pop Flags off Stack . . . . . . . . . . . . . . . . . 28 PUSH - Push Word onto Stack. . . . . . . . . . . . . . . . . . . . 28 PUSHA/PUSHAD - Push All Registers onto Stack (80188+) . . . . . . 28 PUSHF/PUSHFD - Push Flags onto Stack . . . . . . . . . . . . . . . 29 RCL - Rotate Through Carry Left. . . . . . . . . . . . . . . . . . 29 RCR - Rotate Through Carry Right . . . . . . . . . . . . . . . . . 29 REP - Repeat String Operation. . . . . . . . . . . . . . . . . . . 30 REPE/REPZ - Repeat Equal / Repeat Zero . . . . . . . . . . . . . . 30 REPNE/REPNZ - Repeat Not Equal / Repeat Not Zero . . . . . . . . . 30 RET/RETF - Return From Procedure . . . . . . . . . . . . . . . . . 31 ROL - Rotate Left. . . . . . . . . . . . . . . . . . . . . . . . . 31 ROR - Rotate Right . . . . . . . . . . . . . . . . . . . . . . . . 31 SAHF - Store AH Register into FLAGS. . . . . . . . . . . . . . . . 32 SAL/SHL - Shift Arithmetic Left / Shift Logical Left . . . . . . . 32 SAR - Shift Arithmetic Right . . . . . . . . . . . . . . . . . . . 32 SBB - Subtract with Borrow/Carry . . . . . . . . . . . . . . . . . 33 SCAS - Scan String (Byte, Word or Doubleword) . . . . . . . . . . 33 SETAE/SETNB - Set if Above or Equal / Set if Not Below (386+). . . 33 SETB/SETNAE - Set if Below / Set if Not Above or Equal (386+). . . 33 SETBE/SETNA - Set if Below or Equal / Set if Not Above (386+). . . 34 SETE/SETZ - Set if Equal / Set if Zero (386+). . . . . . . . . . . 34 SETNE/SETNZ - Set if Not Equal / Set if Not Zero (386+). . . . . . 34 SETL/SETNGE - Set if Less / Set if Not Greater or Equal (386+) . . 34 SETGE/SETNL - Set if Greater or Equal / Set if Not Less (386+) . . 35 SETLE/SETNG - Set if Less or Equal / Set if Not greater or Equal (386+) 35 SETG/SETNLE - Set if Greater / Set if Not Less or Equal (386+) . . 35 SETS - Set if Signed (386+). . . . . . . . . . . . . . . . . . . . 35 SETNS - Set if Not Signed (386+) . . . . . . . . . . . . . . . . . 36 SETC - Set if Carry (386+) . . . . . . . . . . . . . . . . . . . . 36 SETNC - Set if Not Carry (386+). . . . . . . . . . . . . . . . . . 36 SETO - Set if Overflow (386+). . . . . . . . . . . . . . . . . . . 36 SETNO - Set if Not Overflow (386+) . . . . . . . . . . . . . . . . 36 SETP/SETPE - Set if Parity / Set if Parity Even (386+). . . . . . 37 SETNP/SETPO - Set if No Parity / Set if Parity Odd (386+). . . . . 37 SGDT - Store Global Descriptor Table (286+ privileged) . . . . . . 37 SIDT - Store Interrupt Descriptor Table (286+ privileged). . . . . 37 SHL - Shift Logical Left . . . . . . . . . . . . . . . . . . . . . 37 SHR - Shift Logical Right. . . . . . . . . . . . . . . . . . . . . 38 SHLD/SHRD - Double Precision Shift (386+). . . . . . . . . . . . . 38 SLDT - Store Local Descriptor Table (286+ privileged). . . . . . . 38 SMSW - Store Machine Status Word (286+ privileged) . . . . . . . . 38 STC - Set Carry. . . . . . . . . . . . . . . . . . . . . . . . . . 39 STD - Set Direction Flag . . . . . . . . . . . . . . . . . . . . . 39 STI - Set Interrupt Flag (Enable Interrupts). . . . . . . . . . . 39 STOS - Store String (Byte, Word or Doubleword). . . . . . . . . . 39 STR - Store Task Register (286+ privileged). . . . . . . . . . . . 39 SUB - Subtract . . . . . . . . . . . . . . . . . . . . . . . . . . 40 TEST - Test For Bit Pattern. . . . . . . . . . . . . . . . . . . . 40 VERR - Verify Read (286+ protected). . . . . . . . . . . . . . . . 40 VERW - Verify Write (286+ protected) . . . . . . . . . . . . . . . 40 WAIT/FWAIT - Event Wait. . . . . . . . . . . . . . . . . . . . . . 41 WBINVD - Write-Back and Invalidate Cache (486+). . . . . . . . . . 41 XCHG - Exchange. . . . . . . . . . . . . . . . . . . . . . . . . . 41 XLAT/XLATB - Translate . . . . . . . . . . . . . . . . . . . . . . 41 XOR - Exclusive OR . . . . . . . . . . . . . . . . . . . . . . . . 42 Intel 8086 Family Architecture General Purpose Registers Segment Registers AH/AL AX (EAX) Accumulator CS Code Segment BH/BL BX (EBX) Base DS Data Segment CH/CL CX (ECX) Counter SS Stack Segment DH/DL DX (EDX) Data ES Extra Segment (FS) 386 and newer (Exx) indicates 386+ 32 bit register (GS) 386 and newer Pointer Registers Stack Registers SI (ESI) Source Index SP (ESP) Stack Pointer DI (EDI) Destination Index BP (EBP) Base Pointer IP Instruction Pointer Status Registers FLAGS Status Flags (see FLAGS) Special Registers (386+ only) CR0 Control Register 0 DR0 Debug Register 0 CR2 Control Register 2 DR1 Debug Register 1 CR3 Control Register 3 DR2 Debug Register 2 DR3 Debug Register 3 TR4 Test Register 4 DR6 Debug Register 6 TR5 Test Register 5 DR7 Debug Register 7 TR6 Test Register 6 TR7 Test Register 7 Register Default Segment Valid Overrides BP SS DS, ES, CS SI or DI DS ES, SS, CS DI strings ES None SI strings DS ES, SS, CS - see CPU DETECTING Instruction Timing Instruction Clock Cycle Calculation Some instructions require additional clock cycles due to a "Next Instruction Component" identified by a "+m" in the instruction clock cycle listings. This is due to the prefetch queue being purge on a control transfers. Below is the general rule for calculating "m": 88/86 not applicable 286 "m" is the number of bytes in the next instruction 386 "m" is the number of components in the next instruction (the instruction coding (each byte), plus the data and the displacement are all considered components) 8088/8086 Effective Address (EA) Calculation Description Clock Cycles Displacement 6 Base or Index (BX,BP,SI,DI) 5 Displacement+(Base or Index) 9 Base+Index (BP+DI,BX+SI) 7 Base+Index (BP+SI,BX+DI) 8 Base+Index+Displacement (BP+DI,BX+SI) 11 Base+Index+Displacement (BP+SI+disp,BX+DI+disp) 12 - add 4 cycles for word operands at odd addresses - add 2 cycles for segment override - 80188/80186 timings differ from those of the 8088/8086/80286 Task State Calculation "TS" is defined as switching from VM/486 or 80286 TSS to one of the following: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ New Task ³ ÃÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ´ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´486 TSS³486 TSS³386 TSS³386 TSS³286 TSS³ ³ Old Task ³ (VM=0)³ (VM=1)³ (VM=0)³ (VM=1)³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ´ 386 TSS (VM=0) ³ ³ ³ 309 ³ 226 ³ 282 ³ ÃÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ´ 386 TSS (VM=1) ³ ³ ³ 314 ³ 231 ³ 287 ³ ÃÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ´ 386 CPU/286 TSS ³ ³ ³ 307 ³ 224 ³ 280 ³ ÃÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ´ 486 CPU/286 TSS ³ 199 ³ 177 ³ ³ ³ 180 ³ ÀÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÙ Miscellaneous - all timings are for best case and do not take into account wait states, instruction alignment, the state of the prefetch queue, DMA refresh cycles, cache hits/misses or exception processing. - to convert clocks to nanoseconds divide one microsecond by the processor speed in MegaHertz: (1000MHz/(n MHz)) = X nanoseconds - see 8086 Architecture FLAGS - Intel 8086 Family Flags Register ³11³10³F³E³D³C³B³A³9³8³7³6³5³4³3³2³1³0³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ CF Carry Flag ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ 1 ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ PF Parity Flag ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ 0 ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ AF Auxiliary Flag ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ 0 ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ ZF Zero Flag ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ SF Sign Flag ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ TF Trap Flag (Single Step) ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ IF Interrupt Flag ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄ DF Direction Flag ³ ³ ³ ³ ³ ³ ÀÄÄÄ OF Overflow flag ³ ³ ³ ³ ÀÄÁÄÄÄ IOPL I/O Privilege Level (286+ only) ³ ³ ³ ÀÄÄÄÄÄ NT Nested Task Flag (286+ only) ³ ³ ÀÄÄÄÄÄ 0 ³ ÀÄÄÄÄÄ RF Resume Flag (386+ only) ÀÄÄÄÄÄÄ VM Virtual Mode Flag (386+ only) - see PUSHF POPF STI CLI STD CLD MSW - Machine Status Word (286+ only) ³31³30-5³4³3³2³1³0³ Machine Status Word ³ ³ ³ ³ ³ ³ ÀÄÄÄÄ Protection Enable (PE) ³ ³ ³ ³ ³ ÀÄÄÄÄÄ Math Present (MP) ³ ³ ³ ³ ÀÄÄÄÄÄÄ Emulation (EM) ³ ³ ³ ÀÄÄÄÄÄÄÄ Task Switched (TS) ³ ³ ÀÄÄÄÄÄÄÄÄ Extension Type (ET) ³ ÀÄÄÄÄÄÄÄÄÄÄ Reserved ÀÄÄÄÄÄÄÄÄÄÄÄÄÄ Paging (PG) Bit 0 PE Protection Enable, switches processor between protected and real mode Bit 1 MP Math Present, controls function of the WAIT instruction Bit 2 EM Emulation, indicates whether coprocessor functions are to be emulated Bit 3 TS Task Switched, set and interrogated by coprocessor on task switches and when interpretting coprocessor instructions Bit 4 ET Extension Type, indicates type of coprocessor in system Bits 5-30 Reserved bit 31 PG Paging, indicates whether the processor uses page tables to translate linear addresses to physical addresses - see SMSW LMSW 8086/80186/80286/80386/80486 Instruction Set AAA - Ascii Adjust for Addition Usage: AAA Modifies flags: AF CF (OF,PF,SF,ZF undefined) Changes contents of AL to valid unpacked decimal. The high order nibble is zeroed. Clocks Size Operands 808x 286 386 486 Bytes none 8 3 4 3 1 AAD - Ascii Adjust for Division Usage: AAD Modifies flags: SF ZF PF (AF,CF,OF undefined) Used before dividing unpacked decimal numbers. Multiplies AH by 10 and the adds result into AL. Sets AH to zero. This instruction is also known to have an undocumented behavior. AL := 10*AH+AL AH := 0 Clocks Size Operands 808x 286 386 486 Bytes none 60 14 19 14 2 AAM - Ascii Adjust for Multiplication Usage: AAM Modifies flags: PF SF ZF (AF,CF,OF undefined) AH := AL / 10 AL := AL mod 10 Used after multiplication of two unpacked decimal numbers, this instruction adjusts an unpacked decimal number. The high order nibble of each byte must be zeroed before using this instruction. This instruction is also known to have an undocumented behavior. Clocks Size Operands 808x 286 386 486 Bytes none 83 16 17 15 2 AAS - Ascii Adjust for Subtraction Usage: AAS Modifies flags: AF CF (OF,PF,SF,ZF undefined) Corrects result of a previous unpacked decimal subtraction in AL. High order nibble is zeroed. Clocks Size Operands 808x 286 386 486 Bytes none 8 3 4 3 1 ADC - Add With Carry Usage: ADC dest,src Modifies flags: AF CF OF SF PF ZF Sums two binary operands placing the result in the destination. If CF is set, a 1 is added to the destination. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 7 3 2-4 (W88=24+EA) reg,mem 9+EA 7 6 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=23+EA) accum,immed 4 3 2 1 2-3 ADD - Arithmetic Addition Usage: ADD dest,src Modifies flags: AF CF OF PF SF ZF Adds "src" to "dest" and replacing the original contents of "dest". Both operands are binary. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 7 3 2-4 (W88=24+EA) reg,mem 9+EA 7 6 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=23+EA) accum,immed 4 3 2 1 2-3 AND - Logical And Usage: AND dest,src Modifies flags: CF OF PF SF ZF (AF undefined) Performs a logical AND of the two operands replacing the destination with the result. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 7 3 2-4 (W88=24+EA) reg,mem 9+EA 7 6 1 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=23+EA) accum,immed 4 3 2 1 2-3 ARPL - Adjusted Requested Privilege Level of Selector (286+ PM) Usage: ARPL dest,src (286+ protected mode) Modifies flags: ZF Compares the RPL bits of "dest" against "src". If the RPL bits of "dest" are less than "src", the destination RPL bits are set equal to the source RPL bits and the Zero Flag is set. Otherwise the Zero Flag is cleared. Clocks Size Operands 808x 286 386 486 Bytes reg,reg - 10 20 9 2 mem,reg - 11 21 9 4 BOUND - Array Index Bound Check (80188+) Usage: BOUND src,limit Modifies flags: None Array index in source register is checked against upper and lower bounds in memory source. The first word located at "limit" is the lower boundary and the word at "limit+2" is the upper array bound. Interrupt 5 occurs if the source value is less than or higher than the source. Clocks Size Operands 808x 286 386 486 Bytes reg16,mem32 - nj=13 nj=10 7 2 reg32,mem64 - nj=13 nj=10 7 2 - nj = no jump taken BSF - Bit Scan Forward (386+) Usage: BSF dest,src Modifies flags: ZF Scans source operand for first bit set. Sets ZF if a bit is found set and loads the destination with an index to first set bit. Clears ZF is no bits are found set. BSF scans forward across bit pattern (0-n) while BSR scans in reverse (n-0). Clocks Size Operands 808x 286 386 486 Bytes reg,reg - - 10+3n 6-42 3 reg,mem - - 10+3n 7-43 3-7 reg32,reg32 - - 10+3n 6-42 3-7 reg32,mem32 - - 10+3n 7-43 3-7 BSR - Bit Scan Reverse (386+) Usage: BSR dest,src Modifies flags: ZF Scans source operand for first bit set. Sets ZF if a bit is found set and loads the destination with an index to first set bit. Clears ZF is no bits are found set. BSF scans forward across bit pattern (0-n) while BSR scans in reverse (n-0). Clocks Size Operands 808x 286 386 486 Bytes reg,reg - - 10+3n 6-103 3 reg,mem - - 10+3n 7-104 3-7 reg32,reg32 - - 10+3n 6-103 3-7 reg32,mem32 - - 10+3n 7-104 3-7 BSWAP - Byte Swap (486+) Usage: BSWAP reg32 Modifies flags: none Changes the byte order of a 32 bit register from big endian to little endian or vice versa. Result left in destination register is undefined if the operand is a 16 bit register. Clocks Size Operands 808x 286 386 486 Bytes reg32 - - - 1 2 BT - Bit Test (386+) Usage: BT dest,src Modifies flags: CF The destination bit indexed by the source value is copied into the Carry Flag. Clocks Size Operands 808x 286 386 486 Bytes reg16,immed8 - - 3 3 4-8 mem16,immed8 - - 6 6 4-8 reg16,reg16 - - 3 3 3-7 mem16,reg16 - - 12 12 3-7 BTC - Bit Test with Compliment (386+) Usage: BTC dest,src Modifies flags: CF The destination bit indexed by the source value is copied into the Carry Flag after being complimented (inverted). Clocks Size Operands 808x 286 386 486 Bytes reg16,immed8 - - 6 6 4-8 mem16,immed8 - - 8 8 4-8 reg16,reg16 - - 6 6 3-7 mem16,reg16 - - 13 13 3-7 BTR - Bit Test with Reset (386+) Usage: BTR dest,src Modifies flags: CF The destination bit indexed by the source value is copied into the Carry Flag and then cleared in the destination. Clocks Size Operands 808x 286 386 486 Bytes reg16,immed8 - - 6 6 4-8 mem16,immed8 - - 8 8 4-8 reg16,reg16 - - 6 6 3-7 mem16,reg16 - - 13 13 3-7 BTS - Bit Test and Set (386+) Usage: BTS dest,src Modifies flags: CF The destination bit indexed by the source value is copied into the Carry Flag and then set in the destination. Clocks Size Operands 808x 286 386 486 Bytes reg16,immed8 - - 6 6 4-8 mem16,immed8 - - 8 8 4-8 reg16,reg16 - - 6 6 3-7 mem16,reg16 - - 13 13 3-7 CALL - Procedure Call Usage: CALL destination Modifies flags: None Pushes Instruction Pointer (and Code Segment for far calls) onto stack and loads Instruction Pointer with the address of proc-name. Code continues with execution at CS:IP. Clocks Operands 808x 286 386 486 rel16 (near, IP relative) 19 7 7+m 3 rel32 (near, IP relative) - - 7+m 3 reg16 (near, register indirect) 16 7 7+m 5 reg32 (near, register indirect) - - 7+m 5 mem16 (near, memory indirect) - 21+EA 11 10+m 5 mem32 (near, memory indirect) - - 10+m 5 ptr16:16 (far, full ptr supplied) 28 13 17+m 18 ptr16:32 (far, full ptr supplied) - - 17+m 18 ptr16:16 (far, ptr supplied, prot. mode) - 26 34+m 20 ptr16:32 (far, ptr supplied, prot. mode) - - 34+m 20 m16:16 (far, indirect) 37+EA 16 22+m 17 m16:32 (far, indirect) - - 22+m 17 m16:16 (far, indirect, prot. mode) - 29 38+m 20 m16:32 (far, indirect, prot. mode) - - 38+m 20 ptr16:16 (task, via TSS or task gate) - 177 TS 37+TS m16:16 (task, via TSS or task gate) - 180/185 5+TS 37+TS m16:32 (task) - - TS 37+TS m16:32 (task) - - 5+TS 37+TS ptr16:16 (gate, same privilege) - 41 52+m 35 ptr16:32 (gate, same privilege) - - 52+m 35 m16:16 (gate, same privilege) - 44 56+m 35 m16:32 (gate, same privilege) - - 56+m 35 ptr16:16 (gate, more priv, no parm) - 82 86+m 69 ptr16:32 (gate, more priv, no parm) - - 86+m 69 m16:16 (gate, more priv, no parm) - 83 90+m 69 m16:32 (gate, more priv, no parm) - - 90+m 69 ptr16:16 (gate, more priv, x parms) - 86+4x 94+4x+m 77+4x ptr16:32 (gate, more priv, x parms) - - 94+4x+m 77+4x m16:16 (gate, more priv, x parms) - 90+4x 98+4x+m 77+4x m16:32 (gate, more priv, x parms) - - 98+4x+m 77+4x CBW - Convert Byte to Word Usage: CBW Modifies flags: None Converts byte in AL to word Value in AX by extending sign of AL throughout register AH. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 3 3 1 CDQ - Convert Double to Quad (386+) Usage: CDQ Modifies flags: None Converts signed DWORD in EAX to a signed quad word in EDX:EAX by extending the high order bit of EAX throughout EDX Clocks Size Operands 808x 286 386 486 Bytes none - - 2 3 1 CLC - Clear Carry Usage: CLC Modifies flags: CF Clears the Carry Flag. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 2 1 CLD - Clear Direction Flag Usage: CLD Modifies flags: DF Clears the Direction Flag causing string instructions to increment the SI and DI index registers. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 2 1 CLI - Clear Interrupt Flag (disable) Usage: CLI Modifies flags: IF Disables the maskable hardware interrupts by clearing the Interrupt flag. NMI's and software interrupts are not inhibited. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 3 5 1 CLTS - Clear Task Switched Flag (286+ privileged) Usage: CLTS Modifies flags: None Clears the Task Switched Flag in the Machine Status Register. This is a privileged operation and is generally used only by operating system code. Clocks Size Operands 808x 286 386 486 Bytes none - 2 5 7 2 CMC - Complement Carry Flag Usage: CMC Modifies flags: CF Toggles (inverts) the Carry Flag Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 2 1 CMP - Compare Usage: CMP dest,src Modifies flags: AF CF OF PF SF ZF Subtracts source from destination and updates the flags but does not save result. Flags can subsequently be checked for conditions. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 9+EA 7 5 2 2-4 (W88=13+EA) reg,mem 9+EA 6 6 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 10+EA 6 5 2 3-6 (W88=14+EA) accum,immed 4 3 2 1 2-3 CMPS - Compare String (Byte, Word or Doubleword) Usage: CMPS dest,src CMPSB CMPSW CMPSD (386+) Modifies flags: AF CF OF PF SF ZF Subtracts destination value from source without saving results. Updates flags based on the subtraction and the index registers (E)SI and (E)DI are incremented or decremented depending on the state of the Direction Flag. CMPSB inc/decrements the index registers by 1, CMPSW inc/decrements by 2, while CMPSD increments or decrements by 4. The REP prefixes can be used to process entire data items. Clocks Size Operands 808x 286 386 486 Bytes dest,src 22 8 10 8 1 (W88=30) CMPXCHG - Compare and Exchange Usage: CMPXCHG dest,src (486+) Modifies flags: AF CF OF PF SF ZF Compares the accumulator (8-32 bits) with "dest". If equal the "dest" is loaded with "src", otherwise the accumulator is loaded with "dest". Clocks Size Operands 808x 286 386 486 Bytes reg,reg - - - 6 2 mem,reg - - - 7 2 - add 3 clocks if the "mem,reg" comparison fails CWD - Convert Word to Doubleword Usage: CWD Modifies flags: None Extends sign of word in register AX throughout register DX forming a doubleword quantity in DX:AX. Clocks Size Operands 808x 286 386 486 Bytes none 5 2 2 3 1 CWDE - Convert Word to Extended Doubleword (386+) Usage: CWDE Modifies flags: None Converts a signed word in AX to a signed doubleword in EAX by extending the sign bit of AX throughout EAX. Clocks Size Operands 808x 286 386 486 Bytes none - - 3 3 1 DAA - Decimal Adjust for Addition Usage: DAA Modifies flags: AF CF PF SF ZF (OF undefined) Corrects result (in AL) of a previous BCD addition operation. Contents of AL are changed to a pair of packed decimal digits. Clocks Size Operands 808x 286 386 486 Bytes none 4 3 4 2 1 DAS - Decimal Adjust for Subtraction Usage: DAS Modifies flags: AF CF PF SF ZF (OF undefined) Corrects result (in AL) of a previous BCD subtraction operation. Contents of AL are changed to a pair of packed decimal digits. Clocks Size Operands 808x 286 386 486 Bytes none 4 3 4 2 1 DEC - Decrement Usage: DEC dest Modifies flags: AF OF PF SF ZF Unsigned binary subtraction of one from the destination. Clocks Size Operands 808x 286 386 486 Bytes reg8 3 2 2 1 2 mem 15+EA 7 6 3 2-4 reg16/32 3 2 2 1 1 DIV - Divide Usage: DIV src Modifies flags: (AF,CF,OF,PF,SF,ZF undefined) Unsigned binary division of accumulator by source. If the source divisor is a byte value then AX is divided by "src" and the quotient is placed in AL and the remainder in AH. If source operand is a word value, then DX:AX is divided by "src" and the quotient is stored in AX and the remainder in DX. Clocks Size Operands 808x 286 386 486 Bytes reg8 80-90 14 14 16 2 reg16 144-162 22 22 24 2 reg32 - - 38 40 2 mem8 (86-96)+EA 17 17 16 2-4 mem16 (150-168)+EA 25 25 24 2-4 (W88=158-176+EA) mem32 - - 41 40 2-4 ENTER - Make Stack Frame (80188+) Usage: ENTER locals,level Modifies flags: None Modifies stack for entry to procedure for high level language. Operand "locals" specifies the amount of storage to be allocated on the stack. "Level" specifies the nesting level of the routine. Paired with the LEAVE instruction, this is an efficient method of entry and exit to procedures. Clocks Size Operands 808x 286 386 486 Bytes immed16,0 - 11 10 14 4 immed16,1 - 15 12 17 4 immed16,immed8 - 12+4(n-1) 15+4(n-1) 17+3n 4 ESC - Escape Usage: ESC immed,src Modifies flags: None Provides access to the data bus for other resident processors. The CPU treats it as a NOP but places memory operand on bus. Clocks Size Operands 808x 286 386 486 Bytes immed,reg 2 9-20 ? 2 immed,mem 2 9-20 ? 2-4 HLT - Halt CPU Usage: HLT Modifies flags: None Halts CPU until RESET line is activated, NMI or maskable interrupt received. The CPU becomes dormant but retains the current CS:IP for later restart. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 5 4 1 IDIV - Signed Integer Division Usage: IDIV src Modifies flags: (AF,CF,OF,PF,SF,ZF undefined) Signed binary division of accumulator by source. If source is a byte value, AX is divided by "src" and the quotient is stored in AL and the remainder in AH. If source is a word value, DX:AX is divided by "src", and the quotient is stored in AL and the remainder in DX. Clocks Size Operands 808x 286 386 486 Bytes reg8 101-112 17 19 19 2 reg16 165-184 25 27 27 2 reg32 - - 43 43 2 mem8 (107-118)+EA 20 22 20 2-4 mem16 (171-190)+EA 38 30 28 2-4 (W88=175-194) mem32 - - 46 44 2-4 IMUL - Signed Multiply Usage: IMUL src IMUL src,immed (286+) IMUL dest,src,immed8 (286+) IMUL dest,src (386+) Modifies flags: CF OF (AF,PF,SF,ZF undefined) Signed multiplication of accumulator by "src" with result placed in the accumulator. If the source operand is a byte value, it is multiplied by AL and the result stored in AX. If the source operand is a word value it is multiplied by AX and the result is stored in DX:AX. Other variations of this instruction allow specification of source and destination registers as well as a third immediate factor. Clocks Size Operands 808x 286 386 486 Bytes reg8 80-98 13 9-14 13-18 2 reg16 128-154 21 9-22 13-26 2 reg32 - - 9-38 12-42 2 mem8 86-104 16 12-17 13-18 2-4 mem16 134-160 24 12-25 13-26 2-4 mem32 - - 12-41 13-42 2-4 reg16,reg16 - - 9-22 13-26 3-5 reg32,reg32 - - 9-38 13-42 3-5 reg16,mem16 - - 12-25 13-26 3-5 reg32,mem32 - - 12-41 13-42 3-5 reg16,immed - 21 9-22 13-26 3 reg32,immed - 21 9-38 13-42 3-6 reg16,reg16,immed - 2 9-22 13-26 3-6 reg32,reg32,immed - 21 9-38 13-42 3-6 reg16,mem16,immed - 24 12-25 13-26 3-6 reg32,mem32,immed - 24 12-41 13-42 3-6 IN - Input Byte or Word From Port Usage: IN accum,port Modifies flags: None A byte, word or dword is read from "port" and placed in AL, AX or EAX respectively. If the port number is in the range of 0-255 it can be specified as an immediate, otherwise the port number must be specified in DX. Valid port ranges on the PC are 0-1024, though values through 65535 may be specified and recognized by third party vendors and PS/2's. Clocks Size Operands 808x 286 386 486 Bytes accum,immed8 10/14 5 12 14 2 accum,immed8 (PM) 6/26 8/28/27 2 accum,DX 8/12 5 13 14 1 accum,DX (PM) 7/27 8/28/27 1 - 386+ protected mode timings depend on privilege levels. first number is the timing if: CPL ó IOPL second number is the timing if: CPL > IOPL or in VM 86 mode (386) CPL ò IOPL (486) third number is the timing when: virtual mode on 486 processor - 486 virtual mode always requires 27 cycles INC - Increment Usage: INC dest Modifies flags: AF OF PF SF ZF Adds one to destination unsigned binary operand. Clocks Size Operands 808x 286 386 486 Bytes reg8 3 2 2 1 2 reg16 3 2 2 1 1 reg32 3 2 2 1 1 mem 15+EA 7 6 3 2-4 (W88=23+EA) INS - Input String from Port (80188+) Usage: INS dest,port INSB INSW INSD (386+) Modifies flags: None Loads data from port to the destination ES:(E)DI (even if a destination operand is supplied). (E)DI is adjusted by the size of the operand and increased if the Direction Flag is cleared and decreased if the Direction Flag is set. For INSB, INSW, INSD no operands are allowed and the size is determined by the mnemonic. Clocks Size Operands 808x 286 386 486 Bytes dest,port - 5 15 17 1 dest,port (PM) - 5 9/29 10/32/30 1 none - 5 15 17 1 none (PM) - 5 9/29 10/32/30 1 - 386+ protected mode timings depend on privilege levels. first number is the timing if: CPL ó IOPL second number is the timing if: CPL > IOPL third number is the timing if: virtual mode on 486 processor INT - Interrupt Usage: INT num Modifies flags: TF IF Initiates a software interrupt by pushing the flags, clearing the Trap and Interrupt Flags, pushing CS followed by IP and loading CS:IP with the value found in the interrupt vector table. Execution then begins at the location addressed by the new CS:IP Clocks Size Operands 808x 286 386 486 Bytes 3 (constant) 52/72 23+m 33 26 2 3 (prot. mode, same priv.) - 40+m 59 44 2 3 (prot. mode, more priv.) - 78+m 99 71 2 3 (from VM86 to PL 0) - - 119 82 2 3 (prot. mode via task gate) - 167+m TS 37+TS 2 immed8 51/71 23+m 37 30 1 immed8 (prot. mode, same priv.) - 40+m 59 44 1 immed8 (prot. mode, more priv.) - 78+m 99 71 1 immed8 (from VM86 to PL 0) - - 119 86 1 immed8 (prot. mode, via task gate) - 167+m TS 37+TS 1 INTO - Interrupt on Overflow Usage: INTO Modifies flags: IF TF If the Overflow Flag is set this instruction generates an INT 4 which causes the code addressed by 0000:0010 to be executed. Clocks Size Operands 808x 286 386 486 Bytes none: jump 53/73 24+m 35 28 1 no jump 4 3 3 3 (prot. mode, same priv.) - - 59 46 1 (prot. mode, more priv.) - - 99 73 1 (from VM86 to PL 0) - - 119 84 1 (prot. mode, via task gate) - TS 39+TS 1 INVD - Invalidate Cache (486+) Usage: INVD Modifies flags: none Flushes CPU internal cache. Issues special function bus cycle which indicates to flush external caches. Data in write-back external caches is lost. Clocks Size Operands 808x 286 386 486 Bytes none - - - 4 2 INVLPG - Invalidate Translation Look-Aside Buffer Entry (486+) Usage: INVLPG Modifies flags: none Invalidates a single page table entry in the Translation Look-Aside Buffer. Intel warns that this instruction may be implemented differently on future processors. Clocks Size Operands 808x 286 386 486 Bytes none - - - 12 2 - timing is for TLB entry hit only. IRET/IRETD - Interrupt Return Usage: IRET IRETD (386+) Modifies flags: AF CF DF IF PF SF TF ZF Returns control to point of interruption by popping IP, CS and then the Flags from the stack and continues execution at this location. CPU exception interrupts will return to the instruction that cause the exception because the CS:IP placed on the stack during the interrupt is the address of the offending instruction. Clocks Size Operands 808x 286 386 486 Bytes iret 32/44 17+m 22 15 1 iret (prot. mode) - 31+m 38 15 1 iret (to less privilege) - 55+m 82 36 1 iret (different task, NT=1) - 169+m TS TS+32 1 iretd - - 22/38 15 1 iretd (to less privilege) - - 82 36 1 iretd (to VM86 mode) - - 60 15 1 iretd (different task, NT=1) - - TS TS+32 1 - 386 timings are listed as real-mode/protected-mode Jxx - Jump Instructions Table Mnemonic Meaning Jump Condition JA Jump if Above CF=0 and ZF=0 JAE Jump if Above or Equal CF=0 JB Jump if Below CF=1 JBE Jump if Below or Equal CF=1 or ZF=1 JC Jump if Carry CF=1 JCXZ Jump if CX Zero CX=0 JE Jump if Equal ZF=1 JG Jump if Greater (signed) ZF=0 and SF=OF JGE Jump if Greater or Equal (signed) SF=OF JL Jump if Less (signed) SF != OF JLE Jump if Less or Equal (signed) ZF=1 or SF != OF JMP Unconditional Jump unconditional JNA Jump if Not Above CF=1 or ZF=1 JNAE Jump if Not Above or Equal CF=1 JNB Jump if Not Below CF=0 JNBE Jump if Not Below or Equal CF=0 and ZF=0 JNC Jump if Not Carry CF=0 JNE Jump if Not Equal ZF=0 JNG Jump if Not Greater (signed) ZF=1 or SF != OF JNGE Jump if Not Greater or Equal (signed) SF != OF JNL Jump if Not Less (signed) SF=OF JNLE Jump if Not Less or Equal (signed) ZF=0 and SF=OF JNO Jump if Not Overflow (signed) OF=0 JNP Jump if No Parity PF=0 JNS Jump if Not Signed (signed) SF=0 JNZ Jump if Not Zero ZF=0 JO Jump if Overflow (signed) OF=1 JP Jump if Parity PF=1 JPE Jump if Parity Even PF=1 JPO Jump if Parity Odd PF=0 JS Jump if Signed (signed) SF=1 JZ Jump if Zero ZF=1 Clocks Size Operands 808x 286 386 486 Bytes Jx: jump 16 7+m 7+m 3 2 no jump 4 3 3 1 Jx near-label - - 7+m 3 4 no jump - - 3 1 - It's a good programming practice to organize code so the expected case is executed without a jump since the actual jump takes longer to execute than falling through the test. - see JCXZ and JMP for their respective timings JCXZ/JECXZ - Jump if Register (E)CX is Zero Usage: JCXZ label JECXZ label (386+) Modifies flags: None Causes execution to branch to "label" if register CX is zero. Uses unsigned comparision. Clocks Size Operands 808x 286 386 486 Bytes label: jump 18 8+m 9+m 8 2 no jump 6 4 5 5 JMP - Unconditional Jump Usage: JMP target Modifies flags: None Unconditionally transfers control to "label". Jumps by default are within -32768 to 32767 bytes from the instruction following the jump. NEAR and SHORT jumps cause the IP to be updated while FAR jumps cause CS and IP to be updated. Clocks Operands 808x 286 386 486 rel8 (relative) 15 7+m 7+m 3 rel16 (relative) 15 7+m 7+m 3 rel32 (relative) - - 7+m 3 reg16 (near, register indirect) 11 7+m 7+m 5 reg32 (near, register indirect) - - 7+m 5 mem16 (near, mem indirect) 18+EA 11+m 10+m 5 mem32 (near, mem indirect) 24+EA 15+m 10+m 5 ptr16:16 (far, dword immed) - - 12+m 17 ptr16:16 (far, PM dword immed) - - 27+m 19 ptr16:16 (call gate, same priv.) - 38+m 45+m 32 ptr16:16 (via TSS) - 175+m TS 42+TS ptr16:16 (via task gate) - 180+m TS 43+TS mem16:16 (far, indirect) - - 43+m 13 mem16:16 (far, PM indirect) - - 31+m 18 mem16:16 (call gate, same priv.) - 41+m 49+m 31 mem16:16 (via TSS) - 178+m 5+TS 41+TS mem16:16 (via task gate) - 183+m 5+TS 42+TS ptr16:32 (far, 6 byte immed) - - 12+m 13 ptr16:32 (far, PM 6 byte immed) - - 27+m 18 ptr16:32 (call gate, same priv.) - - 45+m 31 ptr16:32 (via TSS) - - TS 42+TS ptr16:32 (via task state) - - TS 43+TS m16:32 (far, address at dword) - - 43+m 13 m16:32 (far, address at dword) - - 31+m 18 m16:32 (call gate, same priv.) - - 49+m 31 m16:32 (via TSS) - - 5+TS 41+TS m16:32 (via task state) - - 5+TS 42+TS LAHF - Load Register AH From Flags Usage: LAHF Modifies flags: None Copies bits 0-7 of the flags register into AH. This includes flags AF, CF, PF, SF and ZF other bits are undefined. AH := SF ZF xx AF xx PF xx CF Clocks Size Operands 808x 286 386 486 Bytes none 4 2 2 3 1 LAR - Load Access Rights (286+ protected) Usage: LAR dest,src Modifies flags: ZF The high byte of the of the destination register is overwritten by the value of the access rights byte and the low order byte is zeroed depending on the selection in the source operand. The Zero Flag is set if the load operation is successful. Clocks Size Operands 808x 286 386 486 Bytes reg16,reg16 - 14 15 11 3 reg32,reg32 - - 15 11 3 reg16,mem16 - 16 16 11 3-7 reg32,mem32 - - 16 11 3-7 LDS - Load Pointer Using DS Usage: LDS dest,src Modifies flags: None Loads 32-bit pointer from memory source to destination register and DS. The offset is placed in the destination register and the segment is placed in DS. To use this instruction the word at the lower memory address must contain the offset and the word at the higher address must contain the segment. This simplifies the loading of far pointers from the stack and the interrupt vector table. Clocks Size Operands 808x 286 386 486 Bytes reg16,mem32 16+EA 7 7 6 2-4 reg,mem (PM) - - 22 12 5-7 LEA - Load Effective Address Usage: LEA dest,src Modifies flags: None Transfers offset address of "src" to the destination register. Clocks Size Operands 808x 286 386 486 Bytes reg,mem 2+EA 3 2 1 2-4 - the MOV instruction can often save clock cycles when used in place of LEA on 8088 processors LEAVE - Restore Stack for Procedure Exit (80188+) Usage: LEAVE Modifies flags: None Releases the local variables created by the previous ENTER instruction by restoring SP and BP to their condition before the procedure stack frame was initialized. Clocks Size Operands 808x 286 386 486 Bytes none - 5 4 5 1 LES - Load Pointer Using ES Usage: LES dest,src Modifies flags: None Loads 32-bit pointer from memory source to destination register and ES. The offset is placed in the destination register and the segment is placed in ES. To use this instruction the word at the lower memory address must contain the offset and the word at the higher address must contain the segment. This simplifies the loading of far pointers from the stack and the interrupt vector table. Clocks Size Operands 808x 286 386 486 Bytes reg,mem 16+EA 7 7 6 2-4 (W88=24+EA) reg,mem (PM) - - 22 12 5-7 LFS - Load Pointer Using FS (386+) Usage: LFS dest,src Modifies flags: None Loads 32-bit pointer from memory source to destination register and FS. The offset is placed in the destination register and the segment is placed in FS. To use this instruction the word at the lower memory address must contain the offset and the word at the higher address must contain the segment. This simplifies the loading of far pointers from the stack and the interrupt vector table. Clocks Size Operands 808x 286 386 486 Bytes reg,mem - - 7 6 5-7 reg,mem (PM) - - 22 12 5-7 LGDT - Load Global Descriptor Table (286+ privileged) Usage: LGDT src Modifies flags: None Loads a value from an operand into the Global Descriptor Table (GDT) register. Clocks Size Operands 808x 286 386 486 Bytes mem64 - 11 11 11 5 LIDT - Load Interrupt Descriptor Table (286+ privileged) Usage: LIDT src Modifies flags: None Loads a value from an operand into the Interrupt Descriptor Table (IDT) register. Clocks Size Operands 808x 286 386 486 Bytes mem64 - 12 11 11 5 LGS - Load Pointer Using GS (386+) Usage: LGS dest,src Modifies flags: None Loads 32-bit pointer from memory source to destination register and GS. The offset is placed in the destination register and the segment is placed in GS. To use this instruction the word at the lower memory address must contain the offset and the word at the higher address must contain the segment. This simplifies the loading of far pointers from the stack and the interrupt vector table. Clocks Size Operands 808x 286 386 486 Bytes reg,mem - - 7 6 5-7 reg,mem (PM) - - 22 12 5-7 LLDT - Load Local Descriptor Table (286+ privileged) Usage: LLDT src Modifies flags: None Loads a value from an operand into the Local Descriptor Table Register (LDTR). Clocks Size Operands 808x 286 386 486 Bytes reg16 - 17 20 11 3 mem16 - 19 24 11 5 LMSW - Load Machine Status Word (286+ privileged) Usage: LMSW src Modifies flags: None Loads the Machine Status Word (MSW) from data found at "src" Clocks Size Operands 808x 286 386 486 Bytes reg16 - 3 10 13 3 mem16 - 6 13 13 5 LOCK - Lock Bus Usage: LOCK LOCK: (386+ prefix) Modifies flags: None This instruction is a prefix that causes the CPU assert bus lock signal during the execution of the next instruction. Used to avoid two processors from updating the same data location. The 286 always asserts lock during an XCHG with memory operands. This should only be used to lock the bus prior to XCHG, MOV, IN and OUT instructions. Clocks Size Operands 808x 286 386 486 Bytes none 2 0 0 1 1 LODS - Load String (Byte, Word or Double) Usage: LODS src LODSB LODSW LODSD (386+) Modifies flags: None Transfers string element addressed by DS:SI (even if an operand is supplied) to the accumulator. SI is incremented based on the size of the operand or based on the instruction used. If the Direction Flag is set SI is decremented, if the Direction Flag is clear SI is incremented. Use with REP prefixes. Clocks Size Operands 808x 286 386 486 Bytes src 12/16 5 5 5 1 LOOP - Decrement CX and Loop if CX Not Zero Usage: LOOP label Modifies flags: None Decrements CX by 1 and transfers control to "label" if CX is not Zero. The "label" operand must be within -128 or 127 bytes of the instruction following the loop instruction Clocks Size Operands 808x 286 386 486 Bytes label: jump 18 8+m 11+m 6 2 no jump 5 4 ? 2 LOOPE/LOOPZ - Loop While Equal / Loop While Zero Usage: LOOPE label LOOPZ label Modifies flags: None Decrements CX by 1 (without modifying the flags) and transfers control to "label" if CX != 0 and the Zero Flag is set. The "label" operand must be within -128 or 127 bytes of the instruction following the loop instruction. Clocks Size Operands 808x 286 386 486 Bytes label: jump 18 8+m 11+m 9 2 no jump 5 4 ? 6 LOOPNZ/LOOPNE - Loop While Not Zero / Loop While Not Equal Usage: LOOPNZ label LOOPNE label Modifies flags: None Decrements CX by 1 (without modifying the flags) and transfers control to "label" if CX != 0 and the Zero Flag is clear. The "label" operand must be within -128 or 127 bytes of the instruction following the loop instruction. Clocks Size Operands 808x 286 386 486 Bytes label: jump 19 8+m 11+m 9 2 no jump 5 4 ? 6 LSL - Load Segment Limit (286+ protected) Usage: LSL dest,src Modifies flags: ZF Loads the segment limit of a selector into the destination register if the selector is valid and visible at the current privilege level. If loading is successful the Zero Flag is set, otherwise it is cleared. Clocks Size Operands 808x 286 386 486 Bytes reg16,reg16 - 14 20/25 10 3 reg32,reg32 - - 20/25 10 3 reg16,mem16 - 16 21/26 10 5 reg32,mem32 - - 21/26 10 5 - 386 times are listed "byte granular" / "page granular" LSS - Load Pointer Using SS (386+) Usage: LSS dest,src Modifies flags: None Loads 32-bit pointer from memory source to destination register and SS. The offset is placed in the destination register and the segment is placed in SS. To use this instruction the word at the lower memory address must contain the offset and the word at the higher address must contain the segment. This simplifies the loading of far pointers from the stack and the interrupt vector table. Clocks Size Operands 808x 286 386 486 Bytes reg,mem - - 7 6 5-7 reg,mem (PM) - - 22 12 5-7 LTR - Load Task Register (286+ privileged) Usage: LTR src Modifies flags: None Loads the current task register with the value specified in "src". Clocks Size Operands 808x 286 386 486 Bytes reg16 - 17 23 20 3 mem16 - 19 27 20 5 MOV - Move Byte or Word Usage: MOV dest,src Modifies flags: None Copies byte or word from the source operand to the destination operand. If the destination is SS interrupts are disabled except on early buggy 808x CPUs. Some CPUs disable interrupts if the destination is any of the segment registers Clocks Size Operands 808x 286 386 486 Bytes reg,reg 2 2 2 1 2 mem,reg 9+EA 3 2 1 2-4 (W88=13+EA) reg,mem 8+EA 5 4 1 2-4 (W88=12+EA) mem,immed 10+EA 3 2 1 3-6 (W88=14+EA) reg,immed 4 2 2 1 2-3 mem,accum 10 3 2 1 3 (W88=14) accum,mem 10 5 4 1 3 (W88=14) segreg,reg16 2 2 2 3 2 segreg,mem16 8+EA 5 5 9 2-4 (W88=12+EA) reg16,segreg 2 2 2 3 2 mem16,segreg 9+EA 3 2 3 2-4 (W88=13+EA) reg32,CR0/CR2/CR3 - - 6 4 CR0,reg32 - - 10 16 CR2,reg32 - - 4 4 3 CR3,reg32 - - 5 4 3 reg32,DR0/DR1/DR2/DR3 - 22 10 3 reg32,DR6/DR7 - - 22 10 3 DR0/DR1/DR2/DR3,reg32 - 22 11 3 DR6/DR7,reg32 - - 16 11 3 reg32,TR6/TR7 - - 12 4 3 TR6/TR7,reg32 - - 12 4 3 reg32,TR3 3 TR3,reg32 6 - when the 386 special registers are used all operands are 32 bits MOVS - Move String (Byte or Word) Usage: MOVS dest,src MOVSB MOVSW MOVSD (386+) Modifies flags: None Copies data from addressed by DS:SI (even if operands are given) to the location ES:DI destination and updates SI and DI based on the size of the operand or instruction used. SI and DI are incremented when the Direction Flag is cleared and decremented when the Direction Flag is Set. Use with REP prefixes. Clocks Size Operands 808x 286 386 486 Bytes dest,src 18 5 7 7 1 (W88=26) MOVSX - Move with Sign Extend (386+) Usage: MOVSX dest,src Modifies flags: None Copies the value of the source operand to the destination register with the sign extended. Clocks Size Operands 808x 286 386 486 Bytes reg,reg - - 3 3 3 reg,mem - - 6 3 3-7 MOVZX - Move with Zero Extend (386+) Usage: MOVZX dest,src Modifies flags: None Copies the value of the source operand to the destination register with the zeroes extended. Clocks Size Operands 808x 286 386 486 Bytes reg,reg - - 3 3 3 reg,mem - - 6 3 3-7 MUL - Unsigned Multiply Usage: MUL src Modifies flags: CF OF (AF,PF,SF,ZF undefined) Unsigned multiply of the accumulator by the source. If "src" is a byte value, then AL is used as the other multiplicand and the result is placed in AX. If "src" is a word value, then AX is multiplied by "src" and DX:AX receives the result. If "src" is a double word value, then EAX is multiplied by "src" and EDX:EAX receives the result. The 386+ uses an early out algorithm which makes multiplying any size value in EAX as fast as in the 8 or 16 bit registers. Clocks Size Operands 808x 286 386 486 Bytes reg8 70-77 13 9-14 13-18 2 reg16 118-113 21 9-22 13-26 2 reg32 - - 9-38 13-42 2-4 mem8 (76-83)+EA 16 12-17 13-18 2-4 mem16 (124-139)+EA 24 12-25 13-26 2-4 mem32 - - 12-21 13-42 2-4 NEG - Two's Complement Negation Usage: NEG dest Modifies flags: AF CF OF PF SF ZF Subtracts the destination from 0 and saves the 2s complement of "dest" back into "dest". Clocks Size Operands 808x 286 386 486 Bytes reg 3 2 2 1 2 mem 16+EA 7 6 3 2-4 (W88=24+EA) NOP - No Operation (90h) Usage: NOP Modifies flags: None This is a do nothing instruction. It results in occupation of both space and time and is most useful for patching code segments. (This is the original XCHG AL,AL instruction) Clocks Size Operands 808x 286 386 486 Bytes none 3 3 3 1 1 NOT - One's Compliment Negation (Logical NOT) Usage: NOT dest Modifies flags: None Inverts the bits of the "dest" operand forming the 1s complement. Clocks Size Operands 808x 286 386 486 Bytes reg 3 2 2 1 2 mem 16+EA 7 6 3 2-4 (W88=24+EA) OR - Inclusive Logical OR Usage: OR dest,src Modifies flags: CF OF PF SF ZF (AF undefined) Logical inclusive OR of the two operands returning the result in the destination. Any bit set in either operand will be set in the destination. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 7 3 2-4 (W88=24+EA) reg,mem 9+EA 7 6 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem8,immed8 17+EA 7 7 3 3-6 mem16,immed16 25+EA 7 7 3 3-6 accum,immed 4 3 2 1 2-3 OUT - Output Data to Port Usage: OUT port,accum Modifies flags: None Transfers byte in AL,word in AX or dword in EAX to the specified hardware port address. If the port number is in the range of 0-255 it can be specified as an immediate. If greater than 255 then the port number must be specified in DX. Since the PC only decodes 10 bits of the port address, values over 1023 can only be decoded by third party vendor equipment and also map to the port range 0-1023. Clocks Size Operands 808x 286 386 486 Bytes immed8,accum 10/14 3 10 16 2 immed8,accum (PM) - - 4/24 11/31/29 2 DX,accum 8/12 3 11 16 1 DX,accum (PM) - - 5/25 10/30/29 1 - 386+ protected mode timings depend on privilege levels. first number is the timing when: CPL ó IOPL second number is the timing when: CPL > IOPL third number is the timing when: virtual mode on 486 processor OUTS - Output String to Port (80188+) Usage: OUTS port,src OUTSB OUTSW OUTSD (386+) Modifies flags: None Transfers a byte, word or doubleword from "src" to the hardware port specified in DX. For instructions with no operands the "src" is located at DS:SI and SI is incremented or decremented by the size of the operand or the size dictated by the instruction format. When the Direction Flag is set SI is decremented, when clear, SI is incremented. If the port number is in the range of 0-255 it can be specified as an immediate. If greater than 255 then the port number must be specified in DX. Since the PC only decodes 10 bits of the port address, values over 1023 can only be decoded by third party vendor equipment and also map to the port range 0-1023. Clocks Size Operands 808x 286 386 486 Bytes port,src - 5 14 17 1 port,src (PM) - - 8/28 10/32/30 1 - 386+ protected mode timings depend on privilege levels. first number is the timing when: CPL ó IOPL second number is the timing when: CPL > IOPL third number is the timing when: virtual mode on 486 processor POP - Pop Word off Stack Usage: POP dest Modifies flags: None Transfers word at the current stack top (SS:SP) to the destination then increments SP by two to point to the new stack top. CS is not a valid destination. Clocks Size Operands 808x 286 386 486 Bytes reg16 8 5 4 4 1 reg32 4 - - 4 1 segreg 8 5 7 3 1 mem16 17+EA 5 5 6 2-4 mem32 5 - - 6 2-4 POPA/POPAD - Pop All Registers onto Stack (80188+) Usage: POPA POPAD (386+) Modifies flags: None Pops the top 8 words off the stack into the 8 general purpose 16/32 bit registers. Registers are popped in the following order: (E)DI, (E)SI, (E)BP, (E)SP, (E)DX, (E)CX and (E)AX. The (E)SP value popped from the stack is actually discarded. Clocks Size Operands 808x 286 386 486 Bytes none - 19 24 9 1 POPF/POPFD - Pop Flags off Stack Usage: POPF POPFD (386+) Modifies flags: all flags Pops word/doubleword from stack into the Flags Register and then increments SP by 2 (for POPF) or 4 (for POPFD). Clocks Size Operands 808x 286 386 486 Bytes none 8/12 5 5 9 1 (W88=12) none (PM) - - 5 6 1 PUSH - Push Word onto Stack Usage: PUSH src PUSH immed (80188+ only) Modifies flags: None Decrements SP by the size of the operand (two or four, byte values are sign extended) and transfers one word from source to the stack top (SS:SP). Clocks Size Operands 808x 286 386 486 Bytes reg16 11/15 3 2 1 1 reg32 - - 2 1 1 mem16 16+EA 5 5 4 2-4 (W88=24+EA) mem32 - - 5 4 2-4 segreg 10/14 3 2 3 1 immed - 3 2 1 2-3 PUSHA/PUSHAD - Push All Registers onto Stack (80188+) Usage: PUSHA PUSHAD (386+) Modifies flags: None Pushes all general purpose registers onto the stack in the following order: (E)AX, (E)CX, (E)DX, (E)BX, (E)SP, (E)BP, (E)SI, (E)DI. The value of SP is the value before the actual push of SP. Clocks Size Operands 808x 286 386 486 Bytes none - 19 24 11 1 PUSHF/PUSHFD - Push Flags onto Stack Usage: PUSHF PUSHFD (386+) Modifies flags: None Transfers the Flags Register onto the stack. PUSHF saves a 16 bit value while PUSHFD saves a 32 bit value. Clocks Size Operands 808x 286 386 486 Bytes none 10/14 3 4 4 1 none (PM) - - 4 3 1 RCL - Rotate Through Carry Left Usage: RCL dest,count Modifies flags: CF OF ÚÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄþ³C³<þÄÄþ³7 <ÄÄÄÄÄÄÄÄÄÄ 0³<Ä¿ ³ ÀÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Rotates the bits in the destination to the left "count" times with all data pushed out the left side re-entering on the right. The Carry Flag holds the last bit rotated out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 9 3 2 mem,1 15+EA 7 10 4 2-4 (W88=23+EA) reg,CL 8+4n 5+n 9 8-30 2 mem,CL 20+EA+4n 8+n 10 9-31 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 9 8-30 3 mem,immed8 - 8+n 10 9-31 3-5 RCR - Rotate Through Carry Right Usage: RCR dest,count Modifies flags: CF OF ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄ¿ ÚÄ>³7 þÄÄÄÄÄÄÄÄÄ> 0³þÄÄÄ>³C³þÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Rotates the bits in the destination to the right "count" times with all data pushed out the right side re-entering on the left. The Carry Flag holds the last bit rotated out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 9 3 2 mem,1 15+EA 7 10 4 2-4 (W88=23+EA) reg,CL 8+4n 5+n 9 8-30 2 mem,CL 20+EA+4n 8+n 10 9-31 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 9 8-30 3 mem,immed8 - 8+n 10 9-31 3-5 REP - Repeat String Operation Usage: REP Modifies flags: None Repeats execution of string instructions while CX != 0. After each string operation, CX is decremented and the Zero Flag is tested. The combination of a repeat prefix and a segment override on CPU's before the 386 may result in errors if an interrupt occurs before CX=0. The following code shows code that is susceptible to this and how to avoid it: again: rep movs byte ptr ES:[DI],ES:[SI] ; vulnerable instr. jcxz next ; continue if REP successful loop again ; interrupt goofed count next: Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 1 REPE/REPZ - Repeat Equal / Repeat Zero Usage: REPE REPZ Modifies flags: None Repeats execution of string instructions while CX != 0 and the Zero Flag is set. CX is decremented and the Zero Flag tested after each string operation. The combination of a repeat prefix and a segment override on processors other than the 386 may result in errors if an interrupt occurs before CX=0. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 1 REPNE/REPNZ - Repeat Not Equal / Repeat Not Zero Usage: REPNE REPNZ Modifies flags: None Repeats execution of string instructions while CX != 0 and the Zero Flag is clear. CX is decremented and the Zero Flag tested after each string operation. The combination of a repeat prefix and a segment override on processors other than the 386 may result in errors if an interrupt occurs before CX=0. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 1 RET/RETF - Return From Procedure Usage: RET nBytes RETF nBytes RETN nBytes Modifies flags: None Transfers control from a procedure back to the instruction address saved on the stack. "n bytes" is an optional number of bytes to release. Far returns pop the IP followed by the CS, while near returns pop only the IP register. Clocks Size Operands 808x 286 386 486 Bytes retn 16/20 11+m 10+m 5 1 retn immed 20/24 11+m 10+m 5 3 retf 26/34 15+m 18+m 13 1 retf (PM, same priv.) - 32+m 18 1 retf (PM, lesser priv.) - 68 33 1 retf immed 25/33 15+m 18+m 14 3 retf immed (PM, same priv.) 32+m 17 1 retf immed (PM, lesser priv.) 68 33 1 ROL - Rotate Left Usage: ROL dest,count Modifies flags: CF OF ÚÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³C³<þÂÄþ³7 <ÄÄÄÄÄÄÄÄÄÄ 0³<Ä¿ ÀÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Rotates the bits in the destination to the left "count" times with all data pushed out the left side re-entering on the right. The Carry Flag will contain the value of the last bit rotated out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 3 3 2 mem,1 15+EA 7 7 4 2-4 (W88=23+EA) reg,CL 8+4n 5+n 3 3 2 mem,CL 20+EA+4n 8+n 7 4 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 3 2 3 mem,immed8 - 8+n 7 4 3-5 ROR - Rotate Right Usage: ROR dest,count Modifies flags: CF OF ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄ¿ ÚÄ>³7 þÄÄÄÄÄÄÄÄÄ> 0³þÄÂÄ>³C³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Rotates the bits in the destination to the right "count" times with all data pushed out the right side re-entering on the left. The Carry Flag will contain the value of the last bit rotated out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 3 3 2 mem,1 15+EA 7 7 4 2-4 (W88=23+EA) reg,CL 8+4n 5+n 3 3 2 mem,CL 20+EA+4n 8+n 7 4 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 3 2 3 mem,immed8 - 8+n 7 4 3-5 SAHF - Store AH Register into FLAGS Usage: SAHF Modifies flags: AF CF PF SF ZF Transfers bits 0-7 of AH into the Flags Register. This includes AF, CF, PF, SF and ZF. Clocks Size Operands 808x 286 386 486 Bytes none 4 2 3 2 1 SAL/SHL - Shift Arithmetic Left / Shift Logical Left Usage: SAL dest,count SHL dest,count Modifies flags: CF OF PF SF ZF (AF undefined) ÚÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄ¿ ³C³<ÄÄÄþ³7 <ÄÄÄÄÄÄÄÄÄÄ 0³<ÄÄÄþ³0³ ÀÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÙ Shifts the destination left by "count" bits with zeroes shifted in on right. The Carry Flag contains the last bit shifted out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 3 3 2 mem,1 15+EA 7 7 4 2-4 (W88=23+EA) reg,CL 8+4n 5+n 3 3 2 mem,CL 20+EA+4n 8+n 7 4 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 3 2 3 mem,immed8 - 8+n 7 4 3-5 SAR - Shift Arithmetic Right Usage: SAR dest,count Modifies flags: CF OF PF SF ZF (AF undefined) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄ¿ ÚÄþ³7 ÄÄÄÄÄÄÄÄÄÄ> 0³ÄÄÄþ>³C³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÙ ÀÄÄÄ^ Shifts the destination right by "count" bits with the current sign bit replicated in the leftmost bit. The Carry Flag contains the last bit shifted out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 3 3 2 mem,1 15+EA 7 7 4 2-4 (W88=23+EA) reg,CL 8+4n 5+n 3 3 2 mem,CL 20+EA+4n 8+n 7 4 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 3 2 3 mem,immed8 - 8+n 7 4 3-5 SBB - Subtract with Borrow/Carry Usage: SBB dest,src Modifies flags: AF CF OF PF SF ZF Subtracts the source from the destination, and subtracts 1 extra if the Carry Flag is set. Results are returned in "dest". Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 6 3 2-4 (W88=24+EA) reg,mem 9+EA 7 7 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=25+EA) accum,immed 4 3 2 1 2-3 SCAS - Scan String (Byte, Word or Doubleword) Usage: SCAS string SCASB SCASW SCASD (386+) Modifies flags: AF CF OF PF SF ZF Compares value at ES:DI (even if operand is specified) from the accumulator and sets the flags similar to a subtraction. DI is incremented/decremented based on the instruction format (or operand size) and the state of the Direction Flag. Use with REP prefixes. Clocks Size Operands 808x 286 386 486 Bytes string 15 7 7 6 1 (W88=19) SETAE/SETNB - Set if Above or Equal / Set if Not Below (386+) Usage: SETAE dest SETNB dest (unsigned, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Carry Flag is clear otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETB/SETNAE - Set if Below / Set if Not Above or Equal (386+) Usage: SETB dest SETNAE dest (unsigned, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Carry Flag is set otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETBE/SETNA - Set if Below or Equal / Set if Not Above (386+) Usage: SETBE dest SETNA dest (unsigned, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Carry Flag or the Zero Flag is set, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETE/SETZ - Set if Equal / Set if Zero (386+) Usage: SETE dest SETZ dest Modifies flags: none Sets the byte in the operand to 1 if the Zero Flag is set, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETNE/SETNZ - Set if Not Equal / Set if Not Zero (386+) Usage: SETNE dest SETNZ dest Modifies flags: none Sets the byte in the operand to 1 if the Zero Flag is clear, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETL/SETNGE - Set if Less / Set if Not Greater or Equal (386+) Usage: SETL dest SETNGE dest (signed, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Sign Flag is not equal to the Overflow Flag, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETGE/SETNL - Set if Greater or Equal / Set if Not Less (386+) Usage: SETGE dest SETNL dest (signed, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Sign Flag equals the Overflow Flag, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETLE/SETNG - Set if Less or Equal / Set if Not greater or Equal (386+) Usage: SETLE dest SETNG dest (signed, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Zero Flag is set or the Sign Flag is not equal to the Overflow Flag, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETG/SETNLE - Set if Greater / Set if Not Less or Equal (386+) Usage: SETG dest SETNLE dest (signed, 386+) Modifies flags: none Sets the byte in the operand to 1 if the Zero Flag is clear or the Sign Flag equals to the Overflow Flag, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETS - Set if Signed (386+) Usage: SETS dest Modifies flags: none Sets the byte in the operand to 1 if the Sign Flag is set, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETNS - Set if Not Signed (386+) Usage: SETNS dest Modifies flags: none Sets the byte in the operand to 1 if the Sign Flag is clear, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETC - Set if Carry (386+) Usage: SETC dest Modifies flags: none Sets the byte in the operand to 1 if the Carry Flag is set, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETNC - Set if Not Carry (386+) Usage: SETNC dest Modifies flags: none Sets the byte in the operand to 1 if the Carry Flag is clear, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETO - Set if Overflow (386+) Usage: SETO dest Modifies flags: none Sets the byte in the operand to 1 if the Overflow Flag is set, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETNO - Set if Not Overflow (386+) Usage: SETNO dest Modifies flags: none Sets the byte in the operand to 1 if the Overflow Flag is clear, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETP/SETPE - Set if Parity / Set if Parity Even (386+) Usage: SETP dest SETPE dest Modifies flags: none Sets the byte in the operand to 1 if the Parity Flag is set, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SETNP/SETPO - Set if No Parity / Set if Parity Odd (386+) Usage: SETNP dest SETPO dest Modifies flags: none Sets the byte in the operand to 1 if the Parity Flag is clear, otherwise sets the operand to 0. Clocks Size Operands 808x 286 386 486 Bytes reg8 - - 4 3 3 mem8 - - 5 4 3 SGDT - Store Global Descriptor Table (286+ privileged) Usage: SGDT dest Modifies flags: none Stores the Global Descriptor Table (GDT) Register into the specified operand. Clocks Size Operands 808x 286 386 486 Bytes mem64 - 11 9 10 5 SIDT - Store Interrupt Descriptor Table (286+ privileged) Usage: SIDT dest Modifies flags: none Stores the Interrupt Descriptor Table (IDT) Register into the specified operand. Clocks Size Operands 808x 286 386 486 Bytes mem64 - 12 9 10 5 SHL - Shift Logical Left See: SAL SHR - Shift Logical Right Usage: SHR dest,count Modifies flags: CF OF PF SF ZF (AF undefined) ÚÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄ¿ ³0³ÄÄÄþ>³7 ÄÄÄÄÄÄÄÄÄÄ> 0³ÄÄÄþ>³C³ ÀÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÙ Shifts the destination right by "count" bits with zeroes shifted in on the left. The Carry Flag contains the last bit shifted out. Clocks Size Operands 808x 286 386 486 Bytes reg,1 2 2 3 2 mem,1 15+EA 7 7 2-4 (W88=23+EA) reg,CL 8+4n 5+n 3 2 mem,CL 20+EA+4n 8+n 7 2-4 (W88=28+EA+4n) reg,immed8 - 5+n 3 3 mem,immed8 - 8+n 7 3-5 SHLD/SHRD - Double Precision Shift (386+) Usage: SHLD dest,src,count SHRD dest,src,count Modifies flags: CF PF SF ZF (OF,AF undefined) SHLD shifts "dest" to the left "count" times and the bit positions opened are filled with the most significant bits of "src". SHRD shifts "dest" to the right "count" times and the bit positions opened are filled with the least significant bits of the second operand. Only the 5 lower bits of "count" are used. Clocks Size Operands 808x 286 386 486 Bytes reg16,reg16,immed8 - - 3 2 4 reg32,reg32,immed8 - - 3 2 4 mem16,reg16,immed8 - - 7 3 6 mem32,reg32,immed8 - - 7 3 6 reg16,reg16,CL - - 3 3 3 reg32,reg32,CL - - 3 3 3 mem16,reg16,CL - - 7 4 5 mem32,reg32,CL - - 7 4 5 SLDT - Store Local Descriptor Table (286+ privileged) Usage: SLDT dest Modifies flags: none Stores the Local Descriptor Table (LDT) Register into the specified operand. Clocks Size Operands 808x 286 386 486 Bytes reg16 - 2 2 2 3 mem16 - 2 2 3 5 SMSW - Store Machine Status Word (286+ privileged) Usage: SMSW dest Modifies flags: none Store Machine Status Word (MSW) into "dest". Clocks Size Operands 808x 286 386 486 Bytes reg16 - 2 10 2 3 mem16 - 3 3 3 5 STC - Set Carry Usage: STC Modifies flags: CF Sets the Carry Flag to 1. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 2 1 STD - Set Direction Flag Usage: STD Modifies flags: DF Sets the Direction Flag to 1 causing string instructions to auto-decrement SI and DI instead of auto-increment. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 2 1 STI - Set Interrupt Flag (Enable Interrupts) Usage: STI Modifies flags: IF Sets the Interrupt Flag to 1, which enables recognition of all hardware interrupts. If an interrupt is generated by a hardware device, an End of Interrupt (EOI) must also be issued to enable other hardware interrupts of the same or lower priority. Clocks Size Operands 808x 286 386 486 Bytes none 2 2 2 5 1 STOS - Store String (Byte, Word or Doubleword) Usage: STOS dest STOSB STOSW STOSD Modifies flags: None Stores value in accumulator to location at ES:(E)DI (even if operand is given). (E)DI is incremented/decremented based on the size of the operand (or instruction format) and the state of the Direction Flag. Use with REP prefixes. Clocks Size Operands 808x 286 386 486 Bytes dest 11 3 4 5 1 (W88=15) STR - Store Task Register (286+ privileged) Usage: STR dest Modifies flags: None Stores the current Task Register to the specified operand. Clocks Size Operands 808x 286 386 486 Bytes reg16 - 2 2 2 3 mem16 - 3 2 3 5 SUB - Subtract Usage: SUB dest,src Modifies flags: AF CF OF PF SF ZF The source is subtracted from the destination and the result is stored in the destination. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 6 3 2-4 (W88=24+EA) reg,mem 9+EA 7 7 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=25+EA) accum,immed 4 3 2 1 2-3 TEST - Test For Bit Pattern Usage: TEST dest,src Modifies flags: CF OF PF SF ZF (AF undefined) Performs a logical AND of the two operands updating the flags register without saving the result. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 1 1 2 reg,mem 9+EA 6 5 1 2-4 (W88=13+EA) mem,reg 9+EA 6 5 2 2-4 (W88=13+EA) reg,immed 5 3 2 1 3-4 mem,immed 11+EA 6 5 2 3-6 accum,immed 4 3 2 1 2-3 VERR - Verify Read (286+ protected) Usage: VERR src Modifies flags: ZF Verifies the specified segment selector is valid and is readable at the current privilege level. If the segment is readable, the Zero Flag is set, otherwise it is cleared. Clocks Size Operands 808x 286 386 486 Bytes reg16 - 14 10 11 3 mem16 - 16 11 11 5 VERW - Verify Write (286+ protected) Usage: VERW src Modifies flags: ZF Verifies the specified segment selector is valid and is ratable at the current privilege level. If the segment is writable, the Zero Flag is set, otherwise it is cleared. Clocks Size Operands 808x 286 386 486 Bytes reg16 - 14 15 11 3 mem16 - 16 16 11 5 WAIT/FWAIT - Event Wait Usage: WAIT FWAIT Modifies flags: None CPU enters wait state until the coprocessor signals it has finished its operation. This instruction is used to prevent the CPU from accessing memory that may be temporarily in use by the coprocessor. WAIT and FWAIT are identical. Clocks Size Operands 808x 286 386 486 Bytes none 4 3 6+ 1-3 1 WBINVD - Write-Back and Invalidate Cache (486+) Usage: WBINVD Modifies flags: None Flushes internal cache, then signals the external cache to write back current data followed by a signal to flush the external cache. Clocks Size Operands 808x 286 386 486 Bytes none - - - 5 2 XCHG - Exchange Usage: XCHG dest,src Modifies flags: None Exchanges contents of source and destination. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 4 3 3 3 2 mem,reg 17+EA 5 5 5 2-4 (W88=25+EA) reg,mem 17+EA 5 5 3 2-4 (W88=25+EA) accum,reg 3 3 3 3 1 reg,accum 3 3 3 3 1 XLAT/XLATB - Translate Usage: XLAT translation-table XLATB (masm 5.x) Modifies flags: None Replaces the byte in AL with byte from a user table addressed by BX. The original value of AL is the index into the translate table. The best way to discripe this is MOV AL,[BX+AL] Clocks Size Operands 808x 286 386 486 Bytes table offset 11 5 5 4 1 XOR - Exclusive OR Usage: XOR dest,src Modifies flags: CF OF PF SF ZF (AF undefined) Performs a bitwise exclusive OR of the operands and returns the result in the destination. Clocks Size Operands 808x 286 386 486 Bytes reg,reg 3 2 2 1 2 mem,reg 16+EA 7 6 3 2-4 (W88=24+EA) reg,mem 9+EA 7 7 2 2-4 (W88=13+EA) reg,immed 4 3 2 1 3-4 mem,immed 17+EA 7 7 3 3-6 (W88=25+EA) accum,immed 4 3 2 1 2-3 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Testing the Intel CPU Type ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPERATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Believe it or not there are people in the world who still own 8088s! Heck I only just upgraded my 286 to a 486SUX33 a couple of months ago. As we all know, 286's and below just don't make the cut anymore, and even 386's are becoming a thing of the past. Personally I think a program should politely tell someone they are a phleb rather than unceremoniously hanging their machine for them. This text file will show one mothed of detecting the CPU type. Unfortunately I don't have a Pentium op code list so it'll only detect up to a 486. Most of the information in this file came from a well documented assembly program available on various ftp sites called 80486.asm, written by Robert Collins. I tried calling Robert for permission to use his original file, but he appears to have moved house. ÚÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Method ³ ÀÄÄÄÄÄÄÄÄÙ 80186 chips and higher generate an interrupt 6 when they come across an instruction they don't support, this provides us with a real simple method of determining the cpu type (coupled with the trap interrupt it would conceivably also allow us to write a Pentium emulator for the 80286, but that's another story). We simply have to try execting a 486 command, if it causes an interrupt 6 then we know the machine is a 386 or lower. The op codes used in the program below all modify the dx register. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Op Code Machine Language Bytes Supported by ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ shl dx, 5 C1 E2 05 80186 and higher ³ ³ smsw dx 0F 01 E2 80286 and higher ³ ³ mov edx, cr0 0F 20 C2 80386 and higher ³ ³ xadd dx, dx 0F C1 D2 80486 and higher ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ When an interrupt 6 is generated you have to modify the value of the IP register which was pushed onto the stack when the interrupt occurred, otherwise the CPU will go back to it after the interrupt and keep trying to execute it. Each of the instructions in the table above are 3 bytes long so our interrupt handler can simply add 3 to the IP value on the stack. Identifying an 8088 chip is even simpler. If you push the SP register onto the stack the 8088 increments the SP value before it pushes it, the other chips all increment it afterwards. So to test for the presence of an 8088 push SP onto the stack and pop it off into another variable, say AX. If AX and SP are not equal then the chip is an 8088. Keep in mind that you *MUST* check for the presence of an 8088 before doing anything else. Attempting to execute an invalid op code on an 8088 will cause it to hang. Each of the functions in the unit below check for an 8088 first to prevent this happening. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ A Pascal Unit to Test the CPU Type ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The following pascal unit contains some functions your program can use to make sure it's running on the right kind of machine. If your program will only work on a 386 and higher (for example) then put this unit first in your Uses clause and modify the unit's initialization code to terminate the program if the wrong CPU type is detected. The unit's current initialization simply test the CPU type and store it in the 'cpu' variable. { CPUTYPE - A Pascal Unit to Test the CPU Type By Mark Feldman u914097@student.canberra.edu myndale@cairo.anu.edu.au Based on an original assembly program by Robert Collins. } Unit CPUTYPE; Interface const CPU_8088 = 0; CPU_80186 = 1; CPU_80286 = 2; CPU_80386 = 3; CPU_80486 = 4; CPU_UNKNOWN = -1; { The cpu variable is initialised to the cpu type } var cpu : integer; { Isa8088 returns true only if cpu is an 8088 or 8086 } function Isa8088 : boolean; { Isa80186 returns true if cpu is an 80186 or higher } function Isa80186 : boolean; { Isa80286 returns true if cpu is an 80286 or higher } function Isa80286 : boolean; { Isa80386 returns true if cpu is an 80386 or higher } function Isa80386 : boolean; { Isa80486 returns true if cpu is an 80486 or higher } function Isa80486 : boolean; Implementation Uses Dos; var OldIntr6Handler : procedure; valid_op_code : boolean; procedure Intr6Handler; interrupt; begin valid_op_code := false; { Stoopid TP7 won't let me modify IP directly } asm add word ptr ss:[bp + 18], 3 end; end; function Isa8088 : boolean; var sp1, sp2 : word; begin asm mov sp1, sp push sp pop sp2 end; if sp1 <> sp2 then Isa8088 := true else Isa8088 := false; end; function Isa80186 : boolean; begin if Isa8088 then Isa80186 := false else begin valid_op_code := true; GetIntVec(6, @OldIntr6Handler); SetIntVec(6, Addr(Intr6Handler)); inline($C1/$E2/$05); { shl dx, 5 } SetIntVec(6, @OldIntr6Handler); Isa80186 := valid_op_code; end; end; function Isa80286 : boolean; begin if Isa8088 then Isa80286 := false else begin valid_op_code := true; GetIntVec(6, @OldIntr6Handler); SetIntVec(6, Addr(Intr6Handler)); inline($0F/$01/$E2); { smsw dx } SetIntVec(6, @OldIntr6Handler); Isa80286 := valid_op_code; end; end; function Isa80386 : boolean; begin if Isa8088 then Isa80386 := false else begin valid_op_code := true; GetIntVec(6, @OldIntr6Handler); SetIntVec(6, Addr(Intr6Handler)); inline($0F/$20/$C2); { mov edx, cr0 } SetIntVec(6, @OldIntr6Handler); Isa80386 := valid_op_code; end; end; function Isa80486 : boolean; begin if Isa8088 then Isa80486 := false else begin valid_op_code := true; GetIntVec(6, @OldIntr6Handler); SetIntVec(6, Addr(Intr6Handler)); inline($0F/$C1/$D2); { xadd dx, dx } SetIntVec(6, @OldIntr6Handler); Isa80486 := valid_op_code; end; end; begin if Isa8088 then cpu := CPU_8088 else if Isa80486 then cpu := CPU_80486 else if Isa80386 then cpu := CPU_80386 else if Isa80286 then cpu := CPU_80286 else if Isa80186 then cpu := CPU_80186 else cpu := CPU_UNKNOWN; end. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 1 ]==-- þ Introduction Hi there! This is Denthor of ASPHYXIA, AKA Grant Smith. This training program is aimed at all those budding young demo coders out there. I am assuming that the reader is fairly young, has a bit of basic Std. 6 math under his belt, has done a bit of programming before, probably in BASIC, and wants to learn how to write a demo all of his/her own. This I what I am going to do. I am going to describe how certain routines work, and even give you working source code on how you do it. The source code will assume that you have a VGA card that can handle the 320x200x256 mode. I will also assume that you have Turbo Pascal 6.0 or above (this is because some of the code will be in Assembly language, and Turbo Pascal 6.0 makes this incredibly easy to use). By the end of the first "run" of sections, you will be able to code some cool demo stuff all by yourself. The info you need, I will provide to you, but it will be you who decides on the most spectacular way to use it. Why not download some of our demos and see what I'm trying to head you towards. I will be posting one part a week on the Mailbox BBS. I have the first "run" of sections worked out, but if you want me to also do sections on other areas of coding, leave a message to Grant Smith in private E-Mail, or start a conversation here in this conference. I will do a bit of moderating of a sort, and point out things that have been done wrong. In this, the first part, I will show you how you are supposed to set up your Pascal program, how to get into 320x200x256 graphics mode without a BGI file, and various methods of putpixels and a clearscreen utility. NOTE : I drop source code all through my explanations. You needn't try to grab all of it from all over the place, at the end of each part I add a little program that uses all the new routines that we have learned. If you do not fully understand a section, leave me private mail telling me what you don't understand or asking how I got something etc, and I will try to make myself clearer. One last thing : When you spot a mistake I have made in one of my parts, leave me mail and I will correct it post-haste. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Disclaimer Hi again, sorry that I have to add this, but here goes. All source code obtained from this series of instruction programs is used at your own risk. Denthor and the ASPHYXIA demo team hold no responsibility for any loss or damage suffered by anyone through the use of this code. Look guys, the code I'm going to give you has been used by us before in Demos, Applications etc, and we have never had any compliants of machine damage, but if something does go wrong with your computer, don't blame us. Sorry, but that's the way it is. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ The MCGA mode and how you get into it in Pascal without a BGI Lets face it. BGI's are next to worthless for demo coding. It is difficult to find something that is slower then the BGI units for doing graphics. Another thing is, they wern't really meant for 256 color screens anyhow. You have to obtain a specific external 256VGA BGI to get into it in Pascal, and it just doesn't make the grade. So the question remains, how do we get into MCGA 320x200x256 mode in Pascal without a BGI? The answer is simple : Assembly language. Obviously assembly language has loads of functions to handle the VGA card, and this is just one of them. If you look in Norton Gides to Assembly Language, it says this ... ____________________________________________________________________ INT 10h, 00h (0) Set Video Mode Sets the video mode. On entry: AH 00h AL Video mode Returns: None Registers destroyed: AX, SP, BP, SI, DI ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is all well and good, but what does it mean? It means that if you plug in the video mode into AL and call interrupt 10h, SHAZAM! you are in the mode of your choice. Now, the MCGA video mode is mode 13h, and here is how we do it in Pascal. Procedure SetMCGA; BEGIN asm mov ax,0013h int 10h end; END; There you have it! One call to that procedure, and BANG you are in 320x200x256 mode. We can't actually do anything in it yet, so to go back to text mode, you make the video mode equal to 03h, as seen below : Procedure SetText; BEGIN asm mov ax,0003h int 10h end; END; BANG! We are back in text mode! Now, cry all your enquiring minds, what use is this? We can get into the mode, but how do we actually SHOW something on the screen? For that, you must move onto the next section .... =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Clearing the screen to a specific color Now that we are in MCGA mode, how do we clear the screen. The answer is simple : you must just remember that the base adress of the screen is $a000. From $a000, the next 64000 bytes are what is actually displayed on the screen (Note : 320 * 200 = 64000). So to clear the screen, you just use the fillchar command (a basic Pascal command) like so : FillChar (Mem [$a000:0],64000,Col); What the mem command passes the Segment base and the Offset of a part of memory : in this case the screen base is the Segment, and we are starting at the top of the screen; Offset 0. The 64000 is the size of the screen (see above), and Col is a value between 0 and 255, which represents the color you want to clear the screen to. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Putting a pixel on the screen (two different methoods) If you look in Norton Guides about putting a pixel onto the screen, you will see this : ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Writes a pixel dot of a specified color at a specified screen coordinate. On entry: AH 0Ch AL Pixel color CX Horizontal position of pixel DX Vertical position of pixel BH Display page number (graphics modes with more than 1 page) Returns: None Registers destroyed: AX, SP, BP, SI, DI ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ As seen from our SetMCGA example, you would write this by doing the following: Procedure INTPutpixel (X,Y : Integer; Col : Byte); BEGIN asm mov ah,0Ch mov al,[col] mov cx,[x] mov dx,[y] mov bx,[1] int 10h end; END; The X would be the X-Coordinate, the Y would be the Y-Coordinate, and the Col would be the color of the pixel to place. Note that MCGA has 256 colors, numbered 0 to 255. The startoff pallette is pretty grotty, and I will show you how to alter it in my next lesson, but for now you will have to hunt for colors that fit in for what you want to do. Luckily, a byte is 0 to 255, so that is what we pass to the col variable. Have a look at the following. CGA = 4 colours. 4x4 = 16 EGA = 16 colors. 16x16 = 256 VGA = 256 colors. Therefore an EGA is a CGA squared, and a VGA is an EGA squared ;-) Anyway, back to reality. Even though the abouve procedure is written in assembly language, it is slooow. Why? I hear your enquiring minds cry. The reason is simple : It uses interrupts (It calls INT 10h). Interrupts are sloooow ... which is okay for getting into MCGA mode, but not for trying to put down a pixel lickety-split. So, why not try the following ... Procedure MEMPutpixel (X,Y : Integer; Col : Byte); BEGIN Mem [VGA:X+(Y*320)]:=Col; END; The Mem command, as we have seen above, allows you to point at a certain point in memory ... the starting point is $a000, the base of the VGA's memory, and then we specify how far into this base memory we start. Think of the monitor this way. It starts in the top left hand corner at 0. As you increase the number, you start to move across the screen to your right, until you reach 320. At 320, you have gone all the way across the screen and come back out the left side, one pixel down. This carries on until you reach 63999, at the bottom right hand side of the screen. This is how we get the equation X+(Y*320). For every increased Y, we must increment the number by 320. Once we are at the beginning of the Y line we want, we add our X by how far out we want to be. This gives us the exact point in memory that we want to be at, and then we set it equal to the pixel value we want. The MEM methood of putpixel is much faster, and it is shown in the sample program at the end of this lesson. The ASPHYXIA team uses neither putpixel; we use a DMA-Straight-To-Screen-Kill-Yer-Momma-With-An-Axe type putipixel which is FAST. We will give it out, but only to those of you who show us you are serious about coding. If you do do anything, upload it to me, I will be very interested to see it. Remember : If you do glean anything from these training sessions, give us a mention in your demos and UPLOAD YOUR DEMO TO US! Well, after this is the sample program; have fun with it, UNDERSTAND it, and next week I will start on fun with the pallette. See you all later, - Denthor ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ TUTPROG1.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ {$X+} (* This is a handy little trick to know. If you put this at the top of your program, you do not have to set a variable when calling a function, i.e. you may just say 'READKEY' instead of 'CH:=READKEY' *) USES Crt; (* This has a few nice functions in it, such as the READKEY command. *) CONST VGA = $a000; (* This sets the constant VGA to the segment of the VGA screen. *) {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetMCGA; { This procedure gets you into 320x200x256 mode. } BEGIN asm mov ax,0013h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetText; { This procedure returns you to text mode. } BEGIN asm mov ax,0003h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Cls (Col : Byte); { This clears the screen to the specified color } BEGIN Fillchar (Mem [$a000:0],64000,col); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure INTPutpixel (X,Y : Integer; Col : Byte); { This puts a pixel on the screen using interrupts. } BEGIN asm mov ah,0Ch mov al,[col] mov cx,[x] mov dx,[y] mov bx,[1] int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure TestINTPutpixel; { This tests out the speed of the INTPutpixel procedure. } VAR loop1,loop2 : Integer; BEGIN For loop1:=0 to 319 do For loop2:=0 to 199 do INTPutpixel (loop1,loop2,Random (256)); Readkey; Cls (0); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure MEMPutpixel (X,Y : Integer; Col : Byte); { This puts a pixel on the screen by writing directly to memory. } BEGIN Mem [VGA:X+(Y*320)]:=Col; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure TestMEMPutpixel; { This tests out the speed of the MEMPutpixel procedure. } VAR loop1,loop2 : Integer; BEGIN For loop1:=0 to 319 do For loop2:=0 to 199 do MEMPutpixel (loop1,loop2,Random (256)); Readkey; Cls (0); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} BEGIN (* Of the main program *) ClrScr; { This clears the text Screen (CRT unit) } Writeln ('What will happen is that I will clear the screen twice. After'); Writeln ('each clear screen you will have to hit a key. I will then fill'); Writeln ('the screen twice with randomlly colored pixels using two different'); Writeln ('methoods, after each of which you will have to hit a key. I will'); Writeln ('then return you to text mode.'); Writeln; Writeln; Write ('Hit any kay to continue ...'); Readkey; SetMCGA; CLS (32); Readkey; CLS (90); Readkey; TestINTPutpixel; TestMEMPutpixel; SetText; Writeln ('All done. This concludes the first sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the name of GRANT'); Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the'); Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. (* Of the main program *) ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 2 ]==-- þ Introduction Hi there again! This is Grant Smith, AKA Denthor of ASPHYXIA. This is the second part of my Training Program for new programmers. I have only had a lukewarm response to my first part of the trainer series ... remember, if I don't hear from you, I will assume that you are all dead and will stop writing the series ;-). Also, if you do get in contact with me I will give you some of our fast assembly routines which will speed up your demos no end. So go on, leave mail to GRANT SMITH in the main section of the MailBox BBS, start up a discussion or ask a few questions in this Conference, leave mail to ASPHYXIA on the ASPHYXIA BBS, leave mail to Denthor on Connectix, or write to Grant Smith, P.O.Box 270 Kloof 3640 See, there are many ways you can get in contact with me! Use one of them! In this part, I will put the Pallette through it's paces. What the hell is a pallette? How do I find out what it is? How do I set it? How do I stop the "fuzz" that appears on the screen when I change the pallette? How do I black out the screen using the pallette? How do I fade in a screen? How do I fade out a screen? Why are telephone calls so expensive? Most of these quesions will be answered in this, the second part of my Trainer Series for Pascal. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ What is the Pallette? A few weeks ago a friend of mine was playing a computer game. In the game there was a machine with stripes of blue running across it. When the machine was activated, while half of the the blue stripes stayed the same, the other half started to change color and glow. He asked me how two stripes of the same color suddenly become different like that. The answer is simple: the program was changing the pallette. As you know from Part 1, there are 256 colors in MCGA mode, numbered 0 to 255. What you don't know is that each if those colors is made up of different intensities of Red, Green and Blue, the primary colors (you should have learned about the primary colors at school). These intensities are numbers between 0 and 63. The color of bright red would for example be obtained by setting red intensity to 63, green intensity to 0, and blue intensity to 0. This means that two colors can look exactly the same, eg you can set color 10 to bright red and color 78 to color bright red. If you draw a picture using both of those colors, no-one will be able to tell the difference between the two.. It is only when you again change the pallette of either of them will they be able to tell the difference. Also, by changing the whole pallette, you can obtain the "Fade in" and "Fade out" effects found in many demos and games. Pallette manipulation can become quite confusing to some people, because colors that look the same are in fact totally seperate. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I read in the pallette value of a color? This is very easy to do. To read in the pallette value, you enter in the number of the color you want into port $3c7, then read in the values of red, green and blue respectively from port $3c9. Simple, huh? Here is a procedure that does it for you : Procedure GetPal(ColorNo : Byte; Var R,G,B : Byte); { This reads the values of the Red, Green and Blue values of a certain color and returns them to you. } Begin Port[$3c7] := ColorNo; R := Port[$3c9]; G := Port[$3c9]; B := Port[$3c9]; End; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I set the pallette value of a color? This is also as easy as 3.1415926535897932385. What you do is you enter in the number of the color you want to change into port $3c8, then enter the values of red, green and blue respectively into port $3c9. Because you are all so lazy I have written the procedure for you ;-) Procedure Pal(ColorNo : Byte; R,G,B : Byte); { This sets the Red, Green and Blue values of a certain color } Begin Port[$3c8] := ColorNo; Port[$3c9] := R; Port[$3c9] := G; Port[$3c9] := B; End; Asphyxia doesn't use the above pallete procedures, we use assembler versions, which will be given to PEOPLE WHO RESPOND TO THIS TRAINER SERIES (HINT, HINT) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I stop the "fuzz" that appears on my screen when I change the pallette? If you have used the pallette before, you will have noticed that there is quite a bit of "fuzz" on the screen when you change it. The way we counter this is as follows : There is an elctron beam on your monitor that is constantly updating your screen from top to bottom. As it gets to the bottom of the screen, it takes a while for it to get back up to the top of the screen to start updating the screen again. The period where it moves from the bottom to the top is called the Verticle Retrace. During the verticle retrace you may change the pallette without affecting what is on the screen. What we do is that we wait until a verticle retrace has started by calling a certain procedure; this means that everything we do now will only be shown after the verticle retrace, so we can do all sorts of strange and unusual things to the screen during this retrace and only the results will be shown when the retrace is finished. This is way cool, as it means that when we change the pallette, the fuzz doesn't appear on the screen, only the result (the changed pallette), is seen after the retrace! Neat, huh? ;-) I have put the purely assembler WaitRetrace routine in the sample code that follows this message. Use it wisely, my son. NOTE : WaitRetrace can be a great help to your coding ... code that fits into one retrace will mean that the demo will run at the same speed no matter what your computer speed (unless you are doing a lot during the WaitRetrace and the computer is slooooow). Note that in the following sample program and in our SilkyDemo, the thing will run at the same speed whether turbo is on or off. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I black out the screen using the pallette? This is basic : just set the Red, Green and Blue values of all colors to zero intensity, like so : Procedure Blackout; { This procedure blackens the screen by setting the pallette values of all the colors to zero. } VAR loop1:integer; BEGIN WaitRetrace; For loop1:=0 to 255 do Pal (loop1,0,0,0); END; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I fade in a screen? Okay, this can be VERY effective. What you must first do is grab the pallette into a variable, like so : VAR Pall := Array [0.255,1..3] of BYTE; 0 to 255 is for the 256 colors in MCGA mode, 1 to 3 is red, green and blue intensity values; Procedure GrabPallette; VAR loop1:integer; BEGIN For loop1:=0 to 255 do Getpal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]); END; This loads the entire pallette into variable pall. Then you must blackout the screen (see above), and draw what you want to screen without the construction being shown. Then what you do is go throgh the pallette. For each color, you see if the individual intensities are what they should be. If not, you increase them by one unit until they are. Beacuse intensites are in a range from 0 to 63, you only need do this a maximum of 64 times. Procedure Fadeup; VAR loop1,loop2:integer; Tmp : Array [1..3] of byte; { This is temporary storage for the values of a color } BEGIN For loop1:=1 to 64 do BEGIN { A color value for Red, green or blue is 0 to 63, so this loop only need be executed a maximum of 64 times } WaitRetrace; For loop2:=0 to 255 do BEGIN Getpal (loop2,Tmp[1],Tmp[2],Tmp[3]); If Tmp[1]0 then dec (Tmp[1]); If Tmp[2]>0 then dec (Tmp[2]); If Tmp[3]>0 then dec (Tmp[3]); { If the Red, Green or Blue values of color loop2 are not yet zero, then, decrease them by one. } Pal (loop2,Tmp[1],Tmp[2],Tmp[3]); { Set the new, altered pallette color. } END; END; END; Again, to slow the above down, put in a delay above the WaitRetrace. Fading out the screen looks SO much more impressive then just clearing the screen; it can make a world of difference in the impression your demo etc will leave on the people viewing it. To restore the pallette, just do this : Procedure RestorePallette; VAR loop1:integer; BEGIN WaitRetrace; For loop1:=0 to 255 do pal (loop1,Pall[loop1,1],Pall[loop1,2],Pall[loop1,3]); END; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ In closing Well, there are most of those origional questions answered ;-) The following sample program is quite big, so it might take you a while to get around it. Persevere and thou shalt overcome. Pallette manipulation has been a thorn in many coders sides for quite some time, yet hopefully I have shown you all how amazingly simple it is once you have grasped the basics. I need more feedback! In which direction would you like me to head? Is there any particular section you would like more info on? Also, upload me your demo's, however trivial they might seem. We really want to get in contact with/help out new and old coders alike, but you have to leave us that message telling us about yourself and what you have done or want to do. IS THERE ANYBODY OUT THERE!?! P.S. Our new demo should be out soon ... it is going to be GOOOD ... keep an eye out for it. [ And so she came across him, slumped over his keyboard yet again . 'It's three in the morning' she whispered. 'Let's get you to bed'. He stirred, his face bathed in the dull light of his monitor. He mutters something. As she leans across him to disconnect the power, she asks him; 'Was it worth it?'. His answer surprises her. 'No.' he says. In his caffiene-enduced haze, he smiles. 'But it sure is a great way to relax.' ] - Grant Smith Tue 13 July, 1993 2:23 am. See you next week! - Denthor ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ TUTPROG2.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ {$X+} Uses Crt; CONST VGA=$a000; Var Pall,Pall2 : Array[0..255,1..3] of Byte; { This declares the PALL variable. 0 to 255 signify the colors of the pallette, 1 to 3 signifies the Red, Green and Blue values. I am going to use this as a sort of "virtual pallette", and alter it as much as I want, then suddenly bang it to screen. Pall2 is used to "remember" the origional pallette so that we can restore it at the end of the program. } {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetMCGA; { This procedure gets you into 320x200x256 mode. } BEGIN asm mov ax,0013h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetText; { This procedure returns you to text mode. } BEGIN asm mov ax,0003h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure WaitRetrace; assembler; { This waits until you are in a Verticle Retrace ... this means that all screen manipulation you do only appears on screen in the next verticle retrace ... this removes most of the "fuzz" that you see on the screen when changing the pallette. It unfortunately slows down your program by "synching" your program with your monitor card ... it does mean that the program will run at almost the same speed on different speeds of computers which have similar monitors. In our SilkyDemo, we used a WaitRetrace, and it therefore runs at the same (fairly fast) speed when Turbo is on or off. } label l1, l2; asm mov dx,3DAh l1: in al,dx and al,08h jnz l1 l2: in al,dx and al,08h jz l2 end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure GetPal(ColorNo : Byte; Var R,G,B : Byte); { This reads the values of the Red, Green and Blue values of a certain color and returns them to you. } Begin Port[$3c7] := ColorNo; R := Port[$3c9]; G := Port[$3c9]; B := Port[$3c9]; End; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Pal(ColorNo : Byte; R,G,B : Byte); { This sets the Red, Green and Blue values of a certain color } Begin Port[$3c8] := ColorNo; Port[$3c9] := R; Port[$3c9] := G; Port[$3c9] := B; End; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Putpixel (X,Y : Integer; Col : Byte); { This puts a pixel on the screen by writing directly to memory. } BEGIN Mem [VGA:X+(Y*320)]:=Col; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure line(a,b,c,d,col:integer); { This draws a line from a,b to c,d of color col. } Function sgn(a:real):integer; BEGIN if a>0 then sgn:=+1; if a<0 then sgn:=-1; if a=0 then sgn:=0; END; var u,s,v,d1x,d1y,d2x,d2y,m,n:real; i:integer; BEGIN u:= c - a; v:= d - b; d1x:= SGN(u); d1y:= SGN(v); d2x:= SGN(u); d2y:= 0; m:= ABS(u); n := ABS(v); IF NOT (M>N) then BEGIN d2x := 0 ; d2y := SGN(v); m := ABS(v); n := ABS(u); END; s := INT(m / 2); FOR i := 0 TO round(m) DO BEGIN putpixel(a,b,col); s := s + n; IF not (s0 then dec (Tmp[1]); If Tmp[2]>0 then dec (Tmp[2]); If Tmp[3]>0 then dec (Tmp[3]); { If the Red, Green or Blue values of color loop2 are not yet zero, then, decrease them by one. } Pal (loop2,Tmp[1],Tmp[2],Tmp[3]); { Set the new, altered pallette color. } END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure RestorePallette; { This procedure restores the origional pallette } VAR loop1:integer; BEGIN WaitRetrace; For loop1:=0 to 255 do pal (loop1,Pall2[loop1,1],Pall2[loop1,2],Pall2[loop1,3]); END; BEGIN ClrScr; Writeln ('This program will draw lines of different colors across the'); Writeln ('screen and change them only by changing their pallette values.'); Writeln ('The nice thing about using the pallette is that one pallette'); Writeln ('change changes the same color over the whole screen, without'); Writeln ('you having to redraw it. Because I am using a WaitRetrace'); Writeln ('command, turning on and off your turbo during the demonstration'); Writeln ('should have no effect.'); Writeln; Writeln ('The second part of the demo blacks out the screen using the'); Writeln ('pallette, fades in the screen, waits for a keypress, then fades'); Writeln ('it out again. I haven''t put in any delays for the fadein/out,'); Writeln ('so you will have to put ''em in yourself to get it to the speed you'); Writeln ('like. Have fun and enjoy! ;-)'); Writeln; Writeln; Writeln ('Hit any key to continue ...'); Readkey; SetMCGA; GrabPallette; SetUpScreen; repeat PalPlay; { Call the PalPlay procedure repeatedly until a key is pressed. } Until Keypressed; Readkey; { Read in the key pressed otherwise it is left in the keyboard buffer } Blackout; HiddenScreenSetup; FadeUp; Readkey; FadeDown; Readkey; RestorePallette; SetText; Writeln ('All done. This concludes the second sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the name of GRANT'); Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the'); Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 3 ]==-- þ Introduction Greetings! This is the third part of the VGA Trainer series! Sorry it took so long to get out, but I had a running battle with the traffic department for three days to get my car registered, and then the MailBox went down. Ahh, well, life stinks. Anyway, today will do some things vital to most programs : Lines and circles. Watch out for next week's part : Virtual screens. The easy way to eliminate flicker, "doubled sprites", and subjecting the user to watch you building your screen. Almost every ASPHYXIA demo has used a virtual screen (with the exception of the SilkyDemo), so this is one to watch out for. I will also show you how to put all of these loose procedures into units. If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith in private mail here on the Mailbox BBS. 2) Write a message here in the Programming conference here on the Mailbox (Preferred if you have a general programming query or problem others would benefit from) 3) Write to ASPHYXIA on the ASPHYXIA BBS. 4) Write to Denthor, Eze or Livewire on Connectix. 5) Write to : Grant Smith P.O.Box 270 Kloof 3640 6) Call me (Grant Smith) at 73 2129 (leave a message if you call during varsity) NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Circle Algorithim You all know what a circle looks like. But how do you draw one on the computer? You probably know circles drawn with the degrees at these points : 0 ÜÛ|ÛÜ ÛÛÛ|ÛÛÛ 270 ----+---- 90 ÛÛÛ|ÛÛÛ ßÛ|Ûß 180 Sorry about my ASCI ;-) ... anyway, Pascal doesn't work that way ... it works with radians instead of degrees. (You can convert radians to degrees, but I'm not going to go into that now. Note though that in pascal, the circle goes like this : 270 ÜÛ|ÛÜ ÛÛÛ|ÛÛÛ 180 ----+---- 0 ÛÛÛ|ÛÛÛ ßÛ|Ûß 90 Even so, we can still use the famous equations to draw our circle ... (You derive the following by using the theorem of our good friend Pythagoras) Sin (deg) = Y/R Cos (deg) = X/R (This is standard 8(?) maths ... if you haven't reached that level yet, take this to your dad, or if you get stuck leave me a message and I'll do a bit of basic Trig with you. I aim to please ;-)) Where Y = your Y-coord X = your X-coord R = your radius (the size of your circle) deg = the degree To simplify matters, we rewrite the equation to get our X and Y values : Y = R*Sin(deg) X = R*Cos(deg) This obviousy is perfect for us, because it gives us our X and Y co-ords to put into our putpixel routine (see Part 1). Because the Sin and Cos functions return a Real value, we use a round function to transform it into an Integer. Procedure Circle (oX,oY,rad:integer;Col:Byte); VAR deg:real; X,Y:integer; BEGIN deg:=0; repeat X:=round(rad*COS (deg)); Y:=round(rad*sin (deg)); putpixel (x+ox,y+oy,Col); deg:=deg+0.005; until (deg>6.4); END; In the above example, the smaller the amount that deg is increased by, the closer the pixels in the circle will be, but the slower the procedure. 0.005 seem to be best for the 320x200 screen. NOTE : ASPHYXIA does not use this particular circle algorithm, ours is in assembly language, but this one should be fast enough for most. If it isn't, give us the stuff you are using it for and we'll give you ours. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Line algorithms There are many ways to draw a line on the computer. I will describe one and give you two. (The second one you can figure out for yourselves; it is based on the first one but is faster) The first thing you need to do is pass what you want the line to look like to your line procedure. What I have done is said that x1,y1 is the first point on the screen, and x2,y2 is the second point. We also pass the color to the procedure. (Remember the screens top left hand corner is (0,0); see Part 1) Ie. o (X1,Y1) ooooooooo ooooooooo oooooooo (X2,Y2) Again, sorry about my drawings ;-) To find the length of the line, we say the following : XLength = ABS (x1-x2) YLength = ABS (y1-y2) The ABS function means that whatever the result, it will give you an absolute, or posotive, answer. At this stage I set a variable stating wheter the difference between the two x's are negative, zero or posotive. (I do the same for the y's) If the difference is zero, I just use a loop keeping the two with the zero difference posotive, then exit. If neither the x's or y's have a zero difference, I calculate the X and Y slopes, using the following two equations : Xslope = Xlength / Ylength Yslope = Ylength / Xlength As you can see, the slopes are real numbers. NOTE : XSlope = 1 / YSlope Now, there are two ways of drawing the lines : X = XSlope * Y Y = YSlope * X The question is, which one to use? if you use the wrong one, your line will look like this : o o o Instead of this : ooo ooo ooo Well, the solution is as follows : *\``|``/* ***\|/*** ----+---- ***/|\*** */``|``\* If the slope angle is in the area of the stars (*) then use the first equation, if it is in the other section (`) then use the second one. What you do is you calculate the variable on the left hand side by putting the variable on the right hand side in a loop and solving. Below is our finished line routine : Procedure Line (x1,y1,x2,y2:integer;col:byte); VAR x,y,xlength,ylength,dx,dy:integer; xslope,yslope:real; BEGIN xlength:=abs (x1-x2); if (x1-x2)<0 then dx:=-1; if (x1-x2)=0 then dx:=0; if (x1-x2)>0 then dx:=+1; ylength:=abs (y1-y2); if (y1-y2)<0 then dy:=-1; if (y1-y2)=0 then dy:=0; if (y1-y2)>0 then dy:=+1; if (dy=0) then BEGIN if dx<0 then for x:=x1 to x2 do putpixel (x,y1,col); if dx>0 then for x:=x2 to x1 do putpixel (x,y1,col); exit; END; if (dx=0) then BEGIN if dy<0 then for y:=y1 to y2 do putpixel (x1,y,col); if dy>0 then for y:=y2 to y1 do putpixel (x1,y,col); exit; END; xslope:=xlength/ylength; yslope:=ylength/xlength; if (yslope/xslope<1) and (yslope/xslope>-1) then BEGIN if dx<0 then for x:=x1 to x2 do BEGIN y:= round (yslope*x); putpixel (x,y,col); END; if dx>0 then for x:=x2 to x1 do BEGIN y:= round (yslope*x); putpixel (x,y,col); END; END ELSE BEGIN if dy<0 then for y:=y1 to y2 do BEGIN x:= round (xslope*y); putpixel (x,y,col); END; if dy>0 then for y:=y2 to y1 do BEGIN x:= round (xslope*y); putpixel (x,y,col); END; END; END; Quite big, isn't it? Here is a much shorter way of doing much the same thing : function sgn(a:real):integer; begin if a>0 then sgn:=+1; if a<0 then sgn:=-1; if a=0 then sgn:=0; end; procedure line(a,b,c,d,col:integer); var u,s,v,d1x,d1y,d2x,d2y,m,n:real; i:integer; begin u:= c - a; v:= d - b; d1x:= SGN(u); d1y:= SGN(v); d2x:= SGN(u); d2y:= 0; m:= ABS(u); n := ABS(v); IF NOT (M>N) then BEGIN d2x := 0 ; d2y := SGN(v); m := ABS(v); n := ABS(u); END; s := INT(m / 2); FOR i := 0 TO round(m) DO BEGIN putpixel(a,b,col); s := s + n; IF not (s6.4); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Line2 (x1,y1,x2,y2:integer;col:byte); { This draws a line from x1,y1 to x2,y2 using the first method } VAR x,y,xlength,ylength,dx,dy:integer; xslope,yslope:real; BEGIN xlength:=abs (x1-x2); if (x1-x2)<0 then dx:=-1; if (x1-x2)=0 then dx:=0; if (x1-x2)>0 then dx:=+1; ylength:=abs (y1-y2); if (y1-y2)<0 then dy:=-1; if (y1-y2)=0 then dy:=0; if (y1-y2)>0 then dy:=+1; if (dy=0) then BEGIN if dx<0 then for x:=x1 to x2 do putpixel (x,y1,col); if dx>0 then for x:=x2 to x1 do putpixel (x,y1,col); exit; END; if (dx=0) then BEGIN if dy<0 then for y:=y1 to y2 do putpixel (x1,y,col); if dy>0 then for y:=y2 to y1 do putpixel (x1,y,col); exit; END; xslope:=xlength/ylength; yslope:=ylength/xlength; if (yslope/xslope<1) and (yslope/xslope>-1) then BEGIN if dx<0 then for x:=x1 to x2 do BEGIN y:= round (yslope*x); putpixel (x,y,col); END; if dx>0 then for x:=x2 to x1 do BEGIN y:= round (yslope*x); putpixel (x,y,col); END; END ELSE BEGIN if dy<0 then for y:=y1 to y2 do BEGIN x:= round (xslope*y); putpixel (x,y,col); END; if dy>0 then for y:=y2 to y1 do BEGIN x:= round (xslope*y); putpixel (x,y,col); END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure line(a,b,c,d,col:integer); { This draws a line from x1,y1 to x2,y2 using the first method } function sgn(a:real):integer; begin if a>0 then sgn:=+1; if a<0 then sgn:=-1; if a=0 then sgn:=0; end; var u,s,v,d1x,d1y,d2x,d2y,m,n:real; i:integer; begin u:= c - a; v:= d - b; d1x:= SGN(u); d1y:= SGN(v); d2x:= SGN(u); d2y:= 0; m:= ABS(u); n := ABS(v); IF NOT (M>N) then BEGIN d2x := 0 ; d2y := SGN(v); m := ABS(v); n := ABS(u); END; s := INT(m / 2); FOR i := 0 TO round(m) DO BEGIN putpixel(a,b,col); s := s + n; IF not (s ''); end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Setup; { This loads the font and the pallette } VAR f:file; loop1:char; loop2,loop3:integer; BEGIN getmem (font,sizeof (font^)); If exist ('softrock.fnt') then BEGIN Assign (f,'softrock.fnt'); reset (f,1); blockread (f,font^,sizeof (font^)); close (f); Writeln ('SoftRock.FNT from TEXTER5 found in current directory. Using.'); END ELSE BEGIN Writeln ('SoftRock.FNT from TEXTER5 not found in current directory.'); For loop1:=' ' to ']' do For loop2:=1 to 16 do for loop3:=1 to 16 do font^[loop1,loop2,loop3]:=loop2; END; If exist ('pallette.col') then Writeln ('Pallette.COL from TEXTER5 found in current directory. Using.') ELSE Writeln ('Pallette.COL from TEXTER5 not found in current directory.'); Writeln; Writeln; Write ('Hit any key to continue ...'); readkey; setmcga; If exist ('pallette.col') then loadpal ('pallette.col'); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure ScrollMsg (Msg : String); { This scrolls the string in MSG across the screen } Var Loop1,loop2,loop3 : Integer; Begin For loop1:=1 to length (msg) do BEGIN For loop2:=1 to xsize do BEGIN { This bit scrolls the screen by one then puts in the new row of letters } waitretrace; For Loop3 := 100 to 99+ysize do move (mem[vga:1+(loop3*320)],mem[vga:(loop3*320)],319); for loop3:=100 to 99+ysize do putpixel (319,loop3,font^[msg[loop1],loop2,loop3-99],vga); { Change the -99 above to the minimum of loop3-1, which you will change in order to move the position of the scrolly } END; {This next bit scrolls by one pixel after each letter so that there are gaps between the letters } waitretrace; For Loop3 := 100 to 99+ysize do move (mem[vga:1+(loop3*320)],mem[vga:(loop3*320)],319); for loop3:=100 to 99+ysize do putpixel (319,loop3,0,vga); END; End; BEGIN ClrScr; Writeln ('This program will give you an example of a scrolly. If the file'); Writeln ('SOFTROCK.FNT is in the current directory, this program will scroll'); Writeln ('letters, otherwise it will only scroll bars. It also searches for'); Writeln ('PALLETTE.COL, which it uses for it''s pallette. Both SOFTROCK.FNT'); Writeln ('and PALLETTE.COL come with TEXTER5.ZIP, at a BBS near you.'); Writeln; Writeln ('You will note that you can change what the scrolly says merely by'); Writeln ('changing the string in the program.'); Writeln; Setup; repeat ScrollMsg ('ASPHYXIA RULZ!!! '); until keypressed; Settext; freemem (font, sizeof (font^)); Writeln ('All done. This concludes the fifth sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the name of GRANT'); Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the'); Writeln ('ASPHYXIA BBS. Get the numbers from Roblist, or write to :'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 6 ]==-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Introduction Hi there! I'm back, with the latest part in the series : Pregenerated arrays. This is a fairly simple concept that can treble the speed of your code, so have a look. I still suggest that if you haven't got a copy of TEXTER that you get it. This is shareware, written by me, that allows you to grab fonts and use them in your own programs. I downloaded the Friendly City BBS Demo, an intro for a PE BBS, written by a new group called DamnRite, with coder Brett Step. The music was excellent, written by Kon Wilms (If I'm not mistaken, he is an Amiga weenie ;-)). A very nice first production, and I can't wait to see more of their work. I will try con a local BBS to allow me to send Brett some fido-mail. If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith in private mail here on the Mailbox BBS. 2) Write a message here in the Programming conference here on the Mailbox (Preferred if you have a general programming query or problem others would benefit from) 3) Write to ASPHYXIA on the ASPHYXIA BBS. 4) Write to Denthor, Eze or Livewire on Connectix. 5) Write to : Grant Smith P.O.Box 270 Kloof 3640 Natal 6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity) NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Why do I need a lookup table? What is it? A lookup table is an imaginary table in memory where you look up the answers to certain mathematical equations instead of recalculating them each time. This may speed things up considerably. Please note that a lookup table is sometimes referred to as a pregenerated array. One way of looking at a lookup table is as follows : Let us say that for some obscure reason you need to calculate a lot of multiplications (eg. 5*5 , 7*4 , 9*2 etc.). Instead of actually doing a slow multiply each time, you can generate a kind of bonds table, as seen below : ÉÍÑÍËÍÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍÑÍÍÍÍÍÍ» ÇÄÅĶ 1 ³ 2 ³ 3 ³ 4 ³ 5 ³ 6 ³ 7 ³ 8 ³ 9 º ÇÄÁÄ×ÍÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍÍØÍÍÍÍÍ͵ º 1 º 1 ³ 2 ³ 3 ³ 4 ³ 5 ³ 6 ³ 7 ³ 8 ³ 9 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 2 º 2 ³ 4 ³ 6 ³ 8 ³ 10 ³ 12 ³ 14 ³ 16 ³ 18 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 3 º 3 ³ 6 ³ 9 ³ 12 ³ 15 ³ 18 ³ 21 ³ 24 ³ 27 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 4 º 4 ³ 8 ³ 12 ³ 16 ³ 20 ³ 24 ³ 28 ³ 32 ³ 36 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 5 º 5 ³ 10 ³ 15 ³ 20 ³ 25 ³ 30 ³ 35 ³ 40 ³ 45 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 6 º 6 ³ 12 ³ 18 ³ 24 ³ 30 ³ 36 ³ 42 ³ 48 ³ 54 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 7 º 7 ³ 14 ³ 21 ³ 28 ³ 35 ³ 42 ³ 49 ³ 56 ³ 63 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 8 º 8 ³ 16 ³ 24 ³ 32 ³ 40 ³ 48 ³ 56 ³ 64 ³ 72 ³ ÇÄÄÄ×ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄÅÄÄÄÄÄÄ´ º 9 º 9 ³ 18 ³ 27 ³ 36 ³ 45 ³ 54 ³ 63 ³ 72 ³ 81 ³ ÈÍÍÍÊÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÙ This means that instead of calculating 9*4, you just find the 9 on the top and the 4 on the side, and the resulting number is the answer. This type of table is very useful when the equations are very long to do. The example I am going to use for this part is that of circles. Cast your minds back to Part 3 on lines and circles. The circle section took quite a while to finish drawing, mainly because I had to calculate the SIN and COS for EVERY SINGLE POINT. Calculating SIN and COS is obviously very slow, and that was reflected in the speed of the section. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I generate a lookup table? This is very simple. In my example, I am drawing a circle. A circle has 360 degrees, but for greater accuracy, to draw my circle I will start with zero and increase my degrees by 0.4. This means that in each circle there need to be 8000 SINs and COSes (360/0.4=8000). Putting these into the base 64k that Pascal allocates for normal variables is obviously not a happening thing, so we define them as pointers in the following manner: TYPE table = Array [1..8000] of real; VAR sintbl : ^table; costbl : ^table; Then in the program we get the memory for these two pointers. Asphyxia was originally thinking of calling itself Creative Reboot Inc., mainly because we always forgot to get the necessary memory for our pointers. (Though a bit of creative assembly coding also contributed to this. We wound up rating our reboots on a scale of 1 to 10 ;-)). The next obvious step is to place our necessary answers into our lookup tables. This can take a bit of time, so in a demo, you would do it in the very beginning (people just think it's slow disk access or something), or after you have shown a picture (while the viewer is admiring it, you are calculating pi to its 37th degree in the background ;-)) Another way of doing it is, after calculating it once, you save it to a file which you then load into the variable at the beginning of the program. Anyway, this is how we will calculate the table for our circle : Procedure Setup; VAR deg:real; BEGIN deg:=0; for loop1:=1 to 8000 do BEGIN deg:=deg+0.4; costbl^[loop1]:=cos (rad(deg)); sintbl^[loop1]:=sin (rad(deg)); END; END; This will calculate the needed 16000 reals and place them into our two variables. The amount of time this takes is dependant on your computer. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How do I use a lookup table? This is very easy. In your program, wherever you put cos (rad(deg)), you just replace it with : costbl^[deg] Easy, no? Note that the new "deg" variable is now an integer, always between 1 and 8000. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Where else do I use lookup tables? Lookup tables may be used in many different ways. For example, when working out 3-dimensional objects, sin and cos are needed often, and are best put in a lookup table. In a game, you may pregen the course an enemy may take when attacking. Even saving a picture (for example, a plasma screen) after generating it, then loading it up later is a form of pregeneration. When you feel that your program is going much too slow, your problems may be totally sorted out by using a table. Or, maybe not. ;-) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ In closing As you have seen above, lookup tables aren't all that exciting, but they are useful and you need to know how to use them. The attached sample program will demonstrate just how big a difference they can make. Keep on coding, and if you finish anything, let me know about it! I never get any mail, so all mail is greatly appreciated ;-) Sorry, no quote today, it's hot and I'm tired. Maybe next time ;-) - Denthor ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ TUTPROG6.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ {$X+} USES crt; CONST VGA = $a000; TYPE tbl = Array [1..8000] of real; { This will be the shape of the 'table' where we look up values, which is faster then calculating them } VAR loop1:integer; Pall : Array [1..20,1..3] of byte; { This is our temporary pallette. We ony use colors 1 to 20, so we only have variables for those ones. } {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetMCGA; { This procedure gets you into 320x200x256 mode. } BEGIN asm mov ax,0013h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetText; { This procedure returns you to text mode. } BEGIN asm mov ax,0003h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Cls (Col : Byte); { This clears the screen to the specified color } BEGIN Fillchar (Mem [VGA:0],64000,col); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Putpixel (X,Y : Integer; Col : Byte); { This puts a pixel on the screen by writing directly to memory. } BEGIN Mem [VGA:X+(Y*320)]:=Col; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure WaitRetrace; assembler; { This waits for a vertical retrace to reduce snow on the screen } label l1, l2; asm mov dx,3DAh l1: in al,dx and al,08h jnz l1 l2: in al,dx and al,08h jz l2 end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Pal(ColorNo : Byte; R,G,B : Byte); { This sets the Red, Green and Blue values of a certain color } Begin Port[$3c8] := ColorNo; Port[$3c9] := R; Port[$3c9] := G; Port[$3c9] := B; End; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Function rad (theta : real) : real; { This calculates the degrees of an angle } BEGIN rad := theta * pi / 180 END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure NormCirc; { This generates a spireal without using a lookup table } VAR deg,radius:real; x,y:integer; BEGIN gotoxy (1,1); Writeln ('Without pregenerated arrays.'); for loop1:=60 downto 43 do BEGIN deg:=0; radius:=loop1; repeat X:=round(radius*COS (rad (deg))); Y:=round(radius*sin (rad (deg))); putpixel (x+160,y+100,61-loop1); deg:=deg+0.4; { Increase the degree so the circle is round } radius:=radius-0.02; { Decrease the radius for a spiral effect } until radius<0; { Continue till at the centre (the radius is zero) } END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure LookupCirc; { This draws a spiral using a lookup table } VAR radius:real; x,y,pos:integer; costbl : ^tbl; sintbl : ^tbl; Procedure Setupvars; { This is a nested procedure (a procedure in a procedure), and may therefore only be used from within the main part of this procedure. This section gets the memory for the table, then generates the table. } VAR deg:real; BEGIN getmem (costbl,sizeof(costbl^)); getmem (sintbl,sizeof(sintbl^)); deg:=0; for loop1:=1 to 8000 do BEGIN { There are 360 degrees in a } deg:=deg+0.4; { circle. If you increase the } costbl^[loop1]:=cos (rad(deg)); { degrees by 0.4, the number of } sintbl^[loop1]:=sin (rad(deg)); { needed parts of the table is } END; { 360/0.4=8000 } END; { NB : For greater accuracy I increase the degrees by 0.4, because if I increase them by one, holes are left in the final product as a result of the rounding error margin. This means the pregen array is bigger, takes up more memory and is slower to calculate, but the finished product looks better.} BEGIN cls (0); gotoxy (1,1); Writeln ('Generating variables....'); setupvars; gotoxy (1,1); Writeln ('With pregenerated arrays.'); for loop1:=60 downto 43 do BEGIN pos:=1; radius:=loop1; repeat X:=round (radius*costbl^[pos]); { Note how I am not recalculating sin} Y:=round (radius*sintbl^[pos]); { and cos for each point. } putpixel (x+160,y+100,61-loop1); radius:=radius-0.02; inc (pos); if pos>8000 then pos:=1; { I only made a table from 1 to 8000, so it} { must never exceed that, or the program } { will probably crash. } until radius<0; END; freemem (costbl,sizeof(costbl^)); { Freeing the memory taken up by the } freemem (sintbl,sizeof(sintbl^)); { tables. This is very important. } END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure PalPlay; { This procedure mucks about with our "virtual pallette", then shoves it to screen. } Var Tmp : Array[1..3] of Byte; { This is used as a "temporary color" in our pallette } loop1 : Integer; BEGIN Move(Pall[1],Tmp,3); { This copies color 1 from our virtual pallette to the Tmp variable } Move(Pall[2],Pall[1],18*3); { This moves the entire virtual pallette down one color } Move(Tmp,Pall[18],3); { This copies the Tmp variable to no. 18 of the virtual pallette } WaitRetrace; For loop1:=1 to 18 do pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]); END; BEGIN ClrScr; writeln ('Hi there! This program will demonstrate the usefullness of '); writeln ('pregenerated arrays, also known as lookup tables. The program'); writeln ('will first draw a spiral without using a lookup table, rotate'); writeln ('the pallette until a key is pressed, the calculate the lookup'); writeln ('table, then draw the same spiral using the lookup table.'); writeln; writeln ('This is merely one example for the wide range of uses of a '); writeln ('lookup table.'); writeln; writeln; Write (' Hit any key to contine ...'); Readkey; setmcga; directvideo:=FALSE; { This handy trick allows you to use GOTOXY and } { Writeln in GFX mode. Hit CTRL-F1 on it for more } { info/help } For Loop1 := 1 to 18 do BEGIN Pall[Loop1,1] := (Loop1*3)+9; Pall[Loop1,2] := 0; Pall[Loop1,3] := 0; END; { This sets colors 1 to 18 to values between 12 to 63. } WaitRetrace; For loop1:=1 to 18 do pal (loop1,pall[loop1,1],pall[loop1,2],pall[loop1,3]); { This sets the true pallette to variable Pall } normcirc; { This draws a spiral without lookups } Repeat PalPlay; Until keypressed; readkey; lookupcirc; { This draws a spiral with lookups } Repeat PalPlay; Until keypressed; Readkey; SetText; Writeln ('All done. This concludes the sixth sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the name of GRANT'); Writeln ('SMITH on the MailBox BBS, or leave a message to ASPHYXIA on the'); Writeln ('ASPHYXIA BBS. I am also an avid Connectix BBS user.'); Writeln ('Get the numbers from Roblist, or write to :'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 7 ]==-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Introduction Hello! By popular request, this part is all about animation. I will be going over three methods of doing animation on a PC, and will concerntrate specifically on one, which will be demonstrated in the attached sample code. Although not often used in demo coding, animation is usually used in games coding, which can be almost as rewarding ;-) In this part I will also be a lot less stingy with assembler code :) Included will be a fairly fast pure assembler putpixel, an asm screen flip command, an asm icon placer, an asm partial-flip and one or two others. I will be explaining how these work in detail, so this may also be used as a bit of an asm-trainer too. By the way, I apologise for this part taking so long to be released, but I only finished my exams a few days ago, and they of course took preference ;-). I have also noticed that the MailBox BBS is no longer operational, so the trainer will be uploaded regularly to the BBS lists shown at the end of this tutorial. If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail on the ASPHYXIA BBS. 2) Write a message in the Programming conference on the For Your Eyes Only BBS (of which I am the Moderator ) This is preferred if you have a general programming query or problem others would benefit from. 4) Write to Denthor, Eze or Livewire on Connectix. 5) Write to : Grant Smith P.O.Box 270 Kloof 3640 Natal 6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity) 7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and mention the word Denthor near the top of the letter. NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ The Principals of Animation I am sure all of you have seen a computer game with animation at one or other time. There are a few things that an animation sequence must do in order to give an impression of realism. Firstly, it must move, preferably using different frames to add to the realism (for example, with a man walking you should have different frames with the arms an legs in different positions). Secondly, it must not destroy the background, but restore it after it has passed over it. This sounds obvious enough, but can be very difficult to code when you have no idea of how to go about achieving that. In this trainer I will discuss various methods of meeting these two objectives. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Frames and Object Control It is quite obvious that for most animation to succeed, you must have numerous frames of the object in various poses (such as a man with several frames of him walking). When shown one after the other, these give the impression of natural movement. So, how do we store these frames? I hear you cry. Well, the obvious method is to store them in arrays. After drawing a frame in Autodesk Animator and saving it as a .CEL, we usually use the following code to load it in : TYPE icon = Array [1..50,1..50] of byte; VAR tree : icon; Procedure LoadCEL (FileName : string; ScrPtr : pointer); var Fil : file; Buf : array [1..1024] of byte; BlocksRead, Count : word; begin assign (Fil, FileName); reset (Fil, 1); BlockRead (Fil, Buf, 800); { Read and ignore the 800 byte header } Count := 0; BlocksRead := $FFFF; while (not eof (Fil)) and (BlocksRead <> 0) do begin BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead); Count := Count + 1024; end; close (Fil); end; BEGIN Loadcel ('Tree.CEL',addr (tree)); END. We now have the 50x50 picture of TREE.CEL in our array tree. We may access this array in the usual manner (eg. col:=tree [25,30]). If the frame is large, or if you have many frames, try using pointers (see previous parts) Now that we have the picture, how do we control the object? What if we want multiple trees wandering around doing their own thing? The solution is to have a record of information for each tree. A typical data structure may look like the following : TYPE Treeinfo = Record x,y:word; { Where the tree is } speed:byte; { How fast the tree is moving } Direction:byte; { Where the tree is facing } frame:byte { Which animation frame the tree is currently involved in } active:boolean; { Is the tree actually supposed to be shown/used? } END; VAR Forest : Array [1..20] of Treeinfo; You now have 20 trees, each with their own information, location etc. These are accessed using the following means : Forest [15].x:=100; This would set the 15th tree's x coordinate to 100. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Restoring the Overwritten Background I will discuss three methods of doing this. These are NOT NECESSARILY THE ONLY OR BEST WAYS TO DO THIS! You must experiment and decide which is the best for your particular type of program. METHOD 1 : Step 1 : Create two virtual pages, Vaddr and Vaddr2. Step 2 : Draw the background to Vaddr2. Step 3 : Flip Vaddr2 to Vaddr. Step 4 : Draw all the foreground objects onto Vaddr. Step 5 : Flip Vaddr to VGA. Step 6 : Repeat from 3 continuously. In ascii, it looks like follows ... +---------+ +---------+ +---------+ | | | | | | | VGA | <======= | VADDR | <====== | VADDR2 | | | | (bckgnd)| | (bckgnd)| | | |+(icons) | | | +---------+ +---------+ +---------+ The advantages of this approach is that it is straightforward, continual reading of the background is not needed, there is no flicker and it is simple to implement. The disadvantages are that two 64000 byte virtual screens are needed, and the procedure is not very fast because of the slow speed of flipping. METHOD 2 : Step 1 : Draw background to VGA. Step 2 : Grab portion of background that icon will be placed on. Step 3 : Place icon. Step 4 : Replace portion of background from Step 2 over icon. Step 5 : Repeat from step 2 continuously. In terms of ascii ... +---------+ | +--|------- + Background restored (3) | * -|------> * Background saved to memory (1) | ^ | | +--|------- # Icon placed (2) +---------+ The advantages of this method is that very little extra memory is needed. The disadvantages are that writing to VGA is slower then writing to memory, and there may be large amounts of flicker. METHOD 3 : Step 1 : Set up one virtual screen, VADDR. Step 2 : Draw background to VADDR. Step 3 : Flip VADDR to VGA. Step 4 : Draw icon to VGA. Step 5 : Transfer background portion from VADDR to VGA. Step 6 : Repeat from step 4 continuously. In ascii ... +---------+ +---------+ | | | | | VGA | | VADDR | | | | (bckgnd)| | Icon>* <|-----------|--+ | +---------+ +---------+ The advantages are that writing from the virtual screen is quicker then from VGA, and there is less flicker then in Method 2. Disadvantages are that you are using a 64000 byte virtual screen, and flickering occurs with large numbers of objects. In the attached sample program, a mixture of Method 3 and Method 1 is used. It is faster then Method 1, and has no flicker, unlike Method 3. What I do is I use VADDR2 for background, but only restore the background that has been changed to VADDR, before flipping to VGA. In the sample program, you will see that I restore the entire background of each of the icons, and then place all the icons. This is because if I replace the background then place the icon on each object individually, if two objects are overlapping, one is partially overwritten. The following sections are explanations of how the various assembler routines work. This will probably be fairly boring for you if you already know assembler, but should help beginners and dabblers alike. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ The ASM Putpixel To begin with, I will explain a few of the ASM variables and functions : There are 4 register variables : AX,BX,CX,DX. These are words (double bytes) with a range from 0 to 65535. You may access the high and low bytes of these by replacing the X with a "H" for high or "L" for low. For example, AL has a range from 0-255. You also have two pointers : ES:DI and DS:SI. The part on the left is the segment to which you are pointing (eg $a000), and the right hand part is the offset, which is how far into the segment you are pointing. Turbo Pascal places a variable over 16k into the base of a segment, ie. DI or SI will be zero at the start of the variable. If you wish to be pointing to pixel number 3000 on the VGA screen (see previous parts for the layout of the VGA screen), ES would be equal to $a000 and DI would be equal to 3000. You can quite as easily make ES or DS be equal to the offset of a virtual screen. Here are a few functions that you will need to know : mov destination,source This moves the value in source to destination. eg mov ax,50 add destination,source This adds source to destination, the result being stored in destination mul source This multiplies AX by source. If source is a byte, the source is multiplied by AL, the result being stored in AX. If source is a word, the source is multiplied by AX, the result being stored in DX:AX movsb This moves the byte that DS:SI is pointing to into ES:DI, and increments SI and DI. movsw Same as movsb except it moves a word instead of a byte. stosw This moves AX into ES:DI. stosb moves AL into ES:DI. DI is then incremented. push register This saves the value of register by pushing it onto the stack. The register may then be altered, but will be restored to it's original value when popped. pop register This restores the value of a pushed register. NOTE : Pushed values must be popped in the SAME ORDER but REVERSED. rep command This repeats Command by as many times as the value in CX SHL Destination,count ; and SHR Destination,count ; need a bit more explaining. As you know, computers think in ones and zeroes. Each number may be represented in this base 2 operation. A byte consists of 8 ones and zeroes (bits), and have a range from 0 to 255. A word consists of 16 ones and zeroes (bits), and has a range from 0 to 65535. A double word consists of 32 bits. The number 53 may be represented as follows : 00110101. Ask someone who looks clever to explain to you how to convert from binary to decimal and vice-versa. What happens if you shift everything to the left? Drop the leftmost number and add a zero to the right? This is what happens : 00110101 = 53 <----- 01101010 = 106 As you can see, the value has doubled! In the same way, by shifting one to the right, you halve the value! This is a VERY quick way of multiplying or dividing by 2. (note that for dividing by shifting, we get the trunc of the result ... ie. 15 shr 1 = 7) In assembler the format is SHL destination,count This shifts destination by as many bits in count (1=*2, 2=*4, 3=*8, 4=*16 etc) Note that a shift takes only 2 clock cycles, while a mul can take up to 133 clock cycles. Quite a difference, no? Only 286es or above may have count being greater then one. This is why to do the following to calculate the screen coordinates for a putpixel is very slow : mov ax,[Y] mov bx,320 mul bx add ax,[X] mov di,ax But alas! I hear you cry. 320 is not a value you may shift by, as you may only shift by 2,4,8,16,32,64,128,256,512 etc.etc. The solution is very cunning. Watch. mov bx,[X] mov dx,[Y] push bx mov bx, dx {; bx = dx = Y} mov dh, dl {; dh = dl = Y} xor dl, dl {; These 2 lines equal dx*256 } shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (ie y*320)} pop bx {; get back our x} add bx, dx {; finalise location} mov di, bx Let us have a look at this a bit closer shall we? bx=dx=y dx=dx*256 ; bx=bx*64 ( Note, 256+64 = 320 ) dx+bx=Correct y value, just add X! As you can see, in assembler, the shortest code is often not the fastest. The complete putpixel procedure is as follows : Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); { This puts a pixel on the screen by writing directly to memory. } BEGIN Asm push ds {; Make sure these two go out the } push es {; same they went in } mov ax,[where] mov es,ax {; Point to segment of screen } mov bx,[X] mov dx,[Y] push bx {; and this again for later} mov bx, dx {; bx = dx} mov dh, dl {; dx = dx * 256} xor dl, dl shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (ie y*320)} pop bx {; get back our x} add bx, dx {; finalise location} mov di, bx {; di = offset } {; es:di = where to go} xor al,al mov ah, [Col] mov es:[di],ah {; move the value in ah to screen point es:[di] } pop es pop ds End; END; Note that with DI and SI, when you use them : mov di,50 Moves di to position 50 mov [di],50 Moves 50 into the place di is pointing to =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ The Flip Procedure This is fairly straightforward. We get ES:DI to point to the start of the destination screen, and DS:SI to point to the start of the source screen, then do 32000 movsw (64000 bytes). procedure flip(source,dest:Word); { This copies the entire screen at "source" to destination } begin asm push ds mov ax, [Dest] mov es, ax { ES = Segment of source } mov ax, [Source] mov ds, ax { DS = Segment of source } xor si, si { SI = 0 Faster then mov si,0 } xor di, di { DI = 0 } mov cx, 32000 rep movsw { Repeat movsw 32000 times } pop ds end; end; The cls procedure works in much the same way, only it moves the color into AX then uses a rep stosw (see program for details) The PAL command is almost exactly the same as it's Pascal equivalent (see previous tutorials). Look in the sample code to see how it uses the out and in commands. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ In Closing The assembler procedures presented to you in here are not at their best. Most of these are procedures ASPHYXIA abandoned for better ones after months of use. But, as you will soon see, they are all MUCH faster then the original Pascal equivalents I originally gave you. In future, I hope to give you more and more assembler procedures for your ever growing collections. But, as you know, I am not always very prompt with this series (I don't know if even one has been released within one week of the previous one), so if you want to get any stuff done, try do it yourself. What do you have to lose, aside from your temper and a few rather inventive reboots ;-) What should I do for the next trainer? A simple 3-d tutorial? You may not like it, because I would go into minute detail of how it works :) Leave me suggestions for future trainers by any of the means discussed at the top of this trainer. After the customary quote, I will place a listing of the BBSes I currently know that regularly carry this Trainer Series. If your BBS receives it regularly, no matter where in the country you are, get a message to me and I'll add it to the list. Let's make it more convenient for locals to grab a copy without calling long distance ;-) [ There they sit, the preschooler class encircling their mentor, the substitute teacher. "Now class, today we will talk about what you want to be when you grow up. Isn't that fun?" The teacher looks around and spots the child, silent, apart from the others and deep in thought. "Jonny, why don't you start?" she encourages him. Jonny looks around, confused, his train of thought disrupted. He collects himself, and stares at the teacher with a steady eye. "I want to code demos," he says, his words becoming stronger and more confidant as he speaks. "I want to write something that will change peoples perception of reality. I want them to walk away from the computer dazed, unsure of their footing and eyesight. I want to write something that will reach out of the screen and grab them, making heartbeats and breathing slow to almost a halt. I want to write something that, when it is finished, they are reluctant to leave, knowing that nothing they experience that day will be quite as real, as insightful, as good. I want to write demos." Silence. The class and the teacher stare at Jonny, stunned. It is the teachers turn to be confused. Jonny blushes, feeling that something more is required. "Either that or I want to be a fireman." ] - Grant Smith 14:32 21/11/93 See you next time, - DENTHOR These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical) ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ» ºBBS Name ºTelephone No. ºOpen ºMsgºFileºPastº ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹ ºASPHYXIA BBS #1 º(031) 765-5312 ºALL º * º * º * º ºASPHYXIA BBS #2 º(031) 765-6293 ºALL º * º * º * º ºConnectix BBS º(031) 266-9992 ºALL º * º * º * º ºFor Your Eyes Only BBS º(031) 285-318 ºA/H º * º * º * º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ Open = Open at all times or only A/H Msg = Available in message base File = Available in file base Past = Previous Parts available ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ TUTPROG7.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ {$X+} USES crt; CONST VGA = $a000; Type Toastinfo = Record { This is format of of each of our } x,y:integer; { records for the flying toasters } speed,frame:integer; active:boolean; END; icon = Array [1..30*48] of byte; { This is the size of our pictures } Virtual = Array [1..64000] of byte; { The size of our Virtual Screen } VirtPtr = ^Virtual; { Pointer to the virtual screen } CONST frame1 : icon = ( 0,0,0,0,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5, 7,7,7,7,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5, 5,7,7,7,7,7,7,7,8,8,7,7,7,7,7,7,0,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0, 0,0,0,0,0,5,5,7,7,7,7,7,8,8,7,8,8,7,8,7,8,7,7,7,5,8,8,8,8,5,5,5,5,5,5,5,5,5,5,5, 5,0,0,0,0,0,0,0,0,0,0,0,5,7,7,7,7,7,7,8,7,7,7,8,7,7,7,7,7,7,0,0,0,0,0,0,8,5,5,5, 5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,5,7,7,8,8,7,7,8,7,7,8,7,7,7,7,7,0,0,0,0,0, 0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,5,7,8,8,8,7,7,8,7,7,8,7,7,7, 7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,5,7,8,8,8,7,7, 8,8,8,8,8,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9, 9,5,7,8,8,8,8,8,7,7,8,8,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,9,9,9,9,5,7,7,8,8,8,8,7,7,8,8,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, 1,1,0,0,0,1,1,1,1,1,1,1,9,9,9,5,7,8,8,7,7,8,8,7,8,8,8,7,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,5,7,8,8,7,7,7,7,8,8,7,7,7,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,7,8,8,8,8,8,8,8,7, 7,7,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,7, 7,7,7,7,7,7,7,7,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1, 1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, 1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1, 1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4, 4,6,6,6,6,6,6,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,3,3,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ); frame2 : icon = ( 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1, 1,1,0,0,0,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,5, 5,5,5,5,5,5,5,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1, 1,1,1,2,2,2,2,2,5,5,5,5,5,5,5,5,5,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, 1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,5,5,5,5,5,5,5,5,5,2,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,5,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,5,5,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,5,5,5,5, 5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2, 2,2,2,2,2,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,1,1,1,1,1,0,0,0,1,1,1, 1,1,1,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,7,1,4, 4,6,6,6,6,6,6,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0, 0,0,0,5,5,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,5,5,5,5,5,5,5,5,0,0, 0,0,0,0,0,0,0,0,0,0,0,5,5,1,1,1,1,1,1,1,1,1,3,3,1,1,1,1,9,9,9,9,9,9,9,9,9,9,5,5, 5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9, 9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,9,9,9,9,9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5, 1,7,7,1,7,1,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,5,5,1,7,7,7,1,1,5,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,1,1,5,5,5,5,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5, 5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,0,0,0,0,0,0,0,0,0,0,0, 0,0,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ); frame3 : icon = ( 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9, 9,9,9,9,9,9,9,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,9,9,9,9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,7,1,1,1,1,1, 1,1,0,0,0,1,1,1,1,1,1,1,9,9,9,9,9,9,9,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0, 0,7,1,1,7,7,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,5,5,5,1,7,7,7,7,5,5,5,5,5,5, 5,0,0,0,0,0,0,0,7,1,7,7,7,1,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,5,5,1,1,1,7,7, 1,1,7,5,5,5,5,5,5,5,0,0,0,0,0,0,1,1,7,1,1,7,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2, 2,1,7,7,7,1,7,7,7,7,7,5,5,5,5,5,5,5,5,0,0,0,0,0,1,7,7,7,7,1,1,1,1,1,0,0,0,1,1,1, 1,1,1,2,2,2,2,2,2,1,7,7,7,7,7,7,7,1,1,5,5,5,5,5,5,5,5,5,0,0,0,0,7,7,1,7,1,7,1,1, 1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,1,1,1,1,1,1,2,2,5,5,5,5,5,5,5,5,5,5,5,0,0,0, 7,7,7,7,7,1,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,5,5,5,5,5,5, 5,5,5,5,5,0,0,0,7,7,0,0,7,7,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,5,5,0,0,5,5,0,5,5,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1, 1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4, 4,6,6,6,6,6,6,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,3,3,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ); VAR Virscr : VirtPtr; { Our first Virtual screen } VirScr2 : VirtPtr; { Our second Virtual screen } Vaddr : word; { The segment of our virtual screen} Vaddr2 : Word; { The segment of our 2nd virt. screen} ourpal : Array [0..255,1..3] of byte; { A virtual pallette } toaster : Array [1..10] of toastinfo; { The toaster info } {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetMCGA; { This procedure gets you into 320x200x256 mode. } BEGIN asm mov ax,0013h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetText; { This procedure returns you to text mode. } BEGIN asm mov ax,0003h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Cls (Col : Byte; Where:word); { This clears the screen to the specified color } BEGIN asm push es mov cx, 32000; mov es,[where] xor di,di mov al,[col] mov ah,al rep stosw pop es End; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); { This puts a pixel on the screen by writing directly to memory. } BEGIN Asm push ds push es mov ax,[where] mov es,ax mov bx,[X] mov dx,[Y] push bx {; and this again for later} mov bx, dx {; bx = dx} mov dh, dl {; dx = dx * 256} xor dl, dl shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (ie y*320)} pop bx {; get back our x} add bx, dx {; finalise location} mov di, bx {; es:di = where to go} xor al,al mov ah, [Col] mov es:[di],ah pop es pop ds End; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure WaitRetrace; assembler; { This waits for a vertical retrace to reduce snow on the screen } label l1, l2; asm mov dx,3DAh l1: in al,dx and al,08h jnz l1 l2: in al,dx and al,08h jz l2 end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Pal(Col,R,G,B : Byte); { This sets the Red, Green and Blue values of a certain color } Begin asm mov dx,3c8h mov al,[col] out dx,al inc dx mov al,[r] out dx,al mov al,[g] out dx,al mov al,[b] out dx,al end; End; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure GetPal(Col : Byte; Var R,G,B : Byte); { This gets the Red, Green and Blue values of a certain color } Var rr,gg,bb : Byte; Begin asm mov dx,3c7h mov al,col out dx,al add dx,2 in al,dx mov [rr],al in al,dx mov [gg],al in al,dx mov [bb],al end; r := rr; g := gg; b := bb; end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetUpVirtual; { This sets up the memory needed for the virtual screen } BEGIN GetMem (VirScr,64000); vaddr := seg (virscr^); GetMem (VirScr2,64000); vaddr2 := seg (virscr2^); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure ShutDown; { This frees the memory used by the virtual screen } BEGIN FreeMem (VirScr,64000); FreeMem (VirScr2,64000); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure flip(source,dest:Word); { This copies the entire screen at "source" to destination } begin asm push ds mov ax, [Dest] mov es, ax mov ax, [Source] mov ds, ax xor si, si xor di, di mov cx, 32000 rep movsw pop ds end; end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure putico(X,Y:Word;VAR sprt : icon;Where:Word); ASSEMBLER; { This puts an icon, EXCEPT it's color 0 (black) pixels, onto the screen "where", at position X,Y } label _Redraw, _DrawLoop, _Exit, _LineLoop, _NextLine, _Store, _NoPaint; asm push ds push es lds si,Sprt mov ax,X { ax = x } mov bx,Y { bx = y } _Redraw: push ax mov ax,[where] mov es,ax mov ax, bx {; ax = bx x = y} mov bh, bl {; y = y * 256 bx = bx * 256} xor bl, bl shl ax, 1 shl ax, 1 shl ax, 1 shl ax, 1 shl ax, 1 shl ax, 1 {; y = y * 64 ax = ax * 64} add bx, ax {; y = (y*256) + (Y*64) bx = bx + ax (ie y*320)} pop ax {; get back our x} add ax, bx {; finalise location} mov di, ax mov dl,30 { dl = height of sprite } xor ch,ch mov cl,48 { cx = width of sprite } cld push ax mov ax,cx _DrawLoop: push di { store y adr. for later } mov cx,ax { store width } _LineLoop: mov bl,byte ptr [si] or bl,bl jnz _Store _NoPaint: inc si inc di loop _LineLoop jmp _NextLine _Store: movsb loop _LineLoop _NextLine: pop di dec dl jz _Exit add di,320 { di = next line of sprite } jmp _DrawLoop _Exit: pop ax pop es pop ds end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Funny_line(a,b,c,d:integer;where:word); { This procedure draws a line from a,b to c,d on screen "where". After each pixel it plots, it increments a color counter for the next pixel. you may easily alter this to be a normal line procedure, and it will be quite a bit faster than the origional one I gave you. This is because I replaced all the reals with integers. } function sgn(a:real):integer; begin if a>0 then sgn:=+1; if a<0 then sgn:=-1; if a=0 then sgn:=0; end; var i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer; count:integer; begin count:=50; u:= c - a; v:= d - b; d1x:= SGN(u); d1y:= SGN(v); d2x:= SGN(u); d2y:= 0; m:= ABS(u); n := ABS(v); IF NOT (M>N) then BEGIN d2x := 0 ; d2y := SGN(v); m := ABS(v); n := ABS(u); END; s := m shr 1; FOR i := 0 TO m DO BEGIN putpixel(a,b,count,where); inc (count); if count=101 then count:=50; s := s + n; IF not (s0 then putpixel (x+loop2,y+loop3,circ [loop2,loop3],vaddr); END; flip (vaddr,vga); { Copy the entire screen at vaddr, our virtual screen } { on which we have done all our graphics, onto the } { screen you see, VGA } flip (vaddr,vaddr2); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure rotatepal; { This procedure rotates the colors between 50 and 100 } VAR temp : Array [1..3] of byte; loop1:integer; BEGIN Move(OurPal[100],Temp,3); Move(OurPal[50],OurPal[51],50*3); Move(Temp,OurPal[50],3); For loop1:=50 to 100 do pal (loop1,OurPal[loop1,1],OurPal[loop1,2],OurPal[loop1,3]); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure ScreenTrans (x,y:word); { This is a small procedure to copy a 30x30 pixel block from coordinates x,y on the virtual screen to coordinates x,y on the true vga screen } BEGIN asm push ds push es mov ax,vaddr mov es,ax mov ax,vaddr2 mov ds,ax mov bx,[X] mov dx,[Y] push bx {; and this again for later} mov bx, dx {; bx = dx} mov dh, dl {; dx = dx * 256} xor dl, dl shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (ie y*320)} pop bx {; get back our x} add bx, dx {; finalise location} mov di, bx {; es:di = where to go} mov si, di mov al,60 mov bx, 30 { Hight of block to copy } @@1 : mov cx, 24 { Width of block to copy divided by 2 } rep movsw add di,110h { 320 - 48 = 272 .. or 110 in hex } add si,110h dec bx jnz @@1 pop es pop ds end; { I wrote this procedure late last night, so it may not be in it's most optimised state. Sorry :-)} END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure NewToaster; { This adds a new toaster to the screen } VAR loop1:integer; BEGIN loop1:=0; repeat inc (loop1); if not (toaster[loop1].active) then BEGIN toaster[loop1].x:=random (200)+70; toaster[loop1].y:=0; toaster[loop1].active:=true; toaster[loop1].frame:=1; toaster[loop1].speed:=Random (3)+1; loop1:=10; END; until loop1=10; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Fly; { This is the procedure where we move and put the toasters } VAR loop1,loop2:integer; ch:char; BEGIN For loop1:=1 to 10 do toaster[loop1].active:=FALSE; ch:=#0; NewToaster; Repeat if keypressed then BEGIN ch:=readkey; if ch='+' then NewToaster; { If '+' is pressed, add a toaster } if ch='-' then BEGIN { if '-' is pressed, remove a toaster } loop1:=0; repeat inc (loop1); if toaster[loop1].active then BEGIN screentrans (toaster[loop1].x,toaster[loop1].y); toaster [loop1].active:=FALSE; loop1:=10; END; until loop1=10; END; END; for loop1:=1 to 10 do if toaster[loop1].active then BEGIN screentrans (toaster[loop1].x,toaster[loop1].y); { Restore the backgrond the toaster was over } dec (toaster[loop1].x,toaster[loop1].speed); inc (toaster[loop1].y,toaster[loop1].speed); { Move the toaster } if (toaster[loop1].x<1) or (toaster[loop1].y>170) then BEGIN toaster[loop1].active:=FALSE; NewToaster; END; { When toaster reaches the edge of the screen, render it inactive and bring a new one into existance. } END; for loop1:=1 to 10 do if toaster[loop1].active then BEGIN CASE toaster [loop1].frame of 1 : putico (toaster[loop1].x,toaster[loop1].y,frame1,vaddr); 3 : putico (toaster[loop1].x,toaster[loop1].y,frame2,vaddr); 2,4 : putico (toaster[loop1].x,toaster[loop1].y,frame3,vaddr); END; toaster[loop1].frame:=toaster[loop1].frame+1; if toaster [loop1].frame=5 then toaster[loop1].frame:=1; { Draw all the toasters on the VGA screen } END; waitretrace; flip (vaddr,vga); rotatepal; Until ch=#27; END; BEGIN Randomize; { Make sure that the RANDOM funcion really is random } SetupVirtual; { Set up virtual page, VADDR } ClrScr; writeln ('Hello! This program will demonstrate the principals of animation.'); writeln ('The program will firstly generate an arb background screen to a'); writeln ('virtual page, then flip it to the VGA. A toaster will then start'); writeln ('to move across the screen. Note that the background will be restored'); writeln ('after the toaster has passed over it. You may add or remove toasters'); writeln ('by hitting "+" or "-" respectively. Note that the more frames you'); writeln ('use, usually the better the routine looks. Because of space'); writeln ('restrictions, we only had room for three frames.'); writeln; writeln ('The toasters were drawn by Fubar (Pieter Buys) in Autodesk Animator.'); writeln ('I wrote a small little program to convert them into CONSTANTS. See'); writeln ('the main text to find out how to load up AA CEL files directly.'); writeln; writeln; Write (' Hit any key to contine ...'); Readkey; SetMCGA; SetupScreen; { Draw the background screen to VADDR, then flip it to the VGA screen } Fly; { Make the toasters fly around the screen } SetText; ShutDown; { Free the memory taken up by virtual page } Writeln ('All done. This concludes the seventh sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the names of GRANT'); Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid'); Writeln ('Connectix BBS user, which is unfortunatly offline for the moment.'); Writeln ('For discussion purposes, I am also the moderator of the Programming'); Writeln ('newsgroup on the For Your Eyes Only BBS.'); Writeln ('The numbers are available in the main text. You may also write to me at:'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 8 ]==-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Introduction Hello everybody! Christmas is over, the last of the chocolates have been eaten, so it's time to get on with this, the eighth part of the ASPHYXIA Demo Trainer Series. This particular part is primarily about 3-D, but also includes a bit on optimisation. If you are already a 3-D guru, you may as well skip this text file, have a quick look at the sample program then go back to sleep, because I am going to explain in minute detail exactly how the routines work ;) If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail on the ASPHYXIA BBS. 2) Write a message in the Programming conference on the For Your Eyes Only BBS (of which I am the Moderator ) This is preferred if you have a general programming query or problem others would benefit from. 4) Write to Denthor, EzE or Goth on Connectix. 5) Write to : Grant Smith P.O.Box 270 Kloof 3640 Natal 6) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity) 7) Write to mcphail@beastie.cs.und.ac.za on InterNet, and mention the word Denthor near the top of the letter. NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Optimisation Before I begin with the note on 3-D, I would like to stress that many of these routines, and probably most of your own, could be sped up quite a bit with a little optimisation. One must realise, however, that you must take a look at WHAT to optimise ... converting a routine that is only called once at startup into a tightly coded assembler routine may show off your merits as a coder, but does absolutely nothing to speed up your program. Something that is called often per frame is something that needs to be as fast as possible. For some, a much used procedure is the PutPixel procedure. Here is the putpixel procedure I gave you last week: Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); BEGIN Asm push ds { 14 clock ticks } push es { 14 } mov ax,[where] { 8 } mov es,ax { 2 } mov bx,[X] { 8 } mov dx,[Y] { 8 } push bx { 15 } mov bx, dx { 2 } mov dh, dl { 2 } xor dl, dl { 3 } shl bx, 1 { 2 } shl bx, 1 { 2 } shl bx, 1 { 2 } shl bx, 1 { 2 } shl bx, 1 { 2 } shl bx, 1 { 2 } add dx, bx { 3 } pop bx { 12 } add bx, dx { 3 } mov di, bx { 2 } xor al,al { 3 } mov ah, [Col] { 8 } mov es:[di],ah { 10 } pop es { 12 } pop ds { 12 } End; END; Total = 153 clock ticks NOTE : Don't take my clock ticks as gospel, I probably got one or two wrong. Right, now for some optimising. Firstly, if you have 286 instructions turned on, you may replace the 6 shl,1 with shl,6. Secondly, the Pascal compiler automatically pushes and pops ES, so those two lines may be removed. DS:[SI] is not altered in this procedure, so we may remove those too. Also, instead of moving COL into ah, we move it into AL and call stosb (es:[di]:=al; inc di). Let's have a look at the routine now : Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); BEGIN Asm mov ax,[where] { 8 } mov es,ax { 2 } mov bx,[X] { 8 } mov dx,[Y] { 8 } push bx { 15 } mov bx, dx { 2 } mov dh, dl { 2 } xor dl, dl { 3 } shl bx, 6 { 8 } add dx, bx { 3 } pop bx { 12 } add bx, dx { 3 } mov di, bx { 2 } mov al, [Col] { 8 } stosb { 11 } End; END; Total = 95 clock ticks Now, let us move the value of BX directly into DI, thereby removing a costly push and pop. The MOV and the XOR of DX can be replaced by it's equivalent, SHL DX,8 Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); assembler; asm mov ax,[where] { 8 } mov es,ax { 2 } mov bx,[X] { 8 } mov dx,[Y] { 8 } mov di,bx { 2 } mov bx, dx { 2 } shl dx, 8 { 8 } shl bx, 6 { 8 } add dx, bx { 3 } add di, dx { 3 } mov al, [Col] { 8 } stosb { 11 } end; Total = 71 clock ticks As you can see, we have brought the clock ticks down from 153 ticks to 71 ticks ... quite an improvement. (The current ASPHYXIA putpixel takes 48 clock ticks) . As you can see, by going through your routines a few times, you can spot and remove unnecessary instructions, thereby greatly increasing the speed of your program. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Defining a 3-D object Drawing an object in 3-D is not that easy. Sitting down and plotting a list of X,Y and Z points can be a time consuming business. So, let us first look at the three axes you are drawing them on : Y Z /|\ / | / X<-----|-----> | \|/ X is the horisontal axis, from left to right. Y is the vertical axis, from top to bottom. Z is the depth, going straight into the screen. In this trainer, we are using lines, so we define 2 X,Y and Z coordinates, one for each end of the line. A line from far away, in the upper left of the X and Y axes, to close up in the bottom right of the X and Y axes, would look like this : { x1 y1 z1 x2 y2 z2 } ( (-10,10,-10),(10,-10,10) ) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Rotating a point with matrixes NOTE : I thought that more then one matix are matrisese (sp), but my spellchecker insists it is matrixes, so I let it have it's way ;-) Having a 3-D object is useless unless you can rotate it some way. For demonstration purposes, I will begin by working in two dimensions, X and Y. Let us say you have a point, A,B, on a graph. Y | /O1 (Cos (a)*A-Sin (a)*B , Sin (a)*A+Cos (a)*B) |/ (A,B) X<-----|------O--> | | Now, let us say we rotate this point by 45 degrees anti-clockwise. The new A,B can be easily be calculated using sin and cos, by an adaption of our circle algorithm, ie. A2:=Cos (45)*A - Sin (45)*B B2:=Sin (45)*A + Cos (45)*B I recall that in standard 8 and 9, we went rather heavily into this in maths. If you have troubles, fine a 8/9/10 maths book and have a look; it will go through the proofs etc. Anyway, we have now rotated an object in two dimensions, AROUND THE Z AXIS. In matrix form, the equation looks like this : [ Cos (a) -Sin (a) 0 0 ] [ x ] [ Sin (a) Cos (a) 0 0 ] . [ y ] [ 0 0 1 0 ] [ z ] [ 0 0 0 1 ] [ 1 ] I will not go to deeply into matrixes math at this stage, as there are many books on the subject (it is not part of matric maths, however). To multiply a matrix, to add the products of the row of the left matrix and the column of the right matrix, and repeat this for all the columns of the left matrix. I don't explain it as well as my first year maths lecturer, but have a look at how I derived A2 and B2 above. Here are the other matrixes : Matrix for rotation around the Y axis : [ Cos (a) 0 -Sin (a) 0 ] [ x ] [ 0 1 0 0 ] . [ y ] [ Sin (a) 0 Cos (a) 0 ] [ z ] [ 0 0 0 1 ] [ 1 ] Matrix for rotation around the X axis : [ 1 0 0 ] [ x ] [ 0 Cos (a) -Sin (a) 0 ] . [ y ] [ 0 Sin (a) Cos (a) 0 ] [ z ] [ 0 0 0 1 ] [ 1 ] By putting all these matrixes together, we can translate out 3D points around the origin of 0,0,0. See the sample program for how we put them together. In the sample program, we have a constant, never changing base object. This is rotated into a second variable, which is then drawn. I am sure many of you can thing of cool ways to change the base object, the effects of which will appear while the object is rotating. One idea is to "pulsate" a certain point of the object according to the beat of the music being played in the background. Be creative. If you feel up to it, you could make your own version of transformers ;) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Drawing a 3D point to screen Having a rotated 3D object is useless unless we can draw it to screen. But how do we show a 3D point on a 2D screen? The answer needs a bit of explaining. Examine the following diagram : | ________------------- ____|___------ o Object at X,Y,Z o1 Object at X,Y,Z2 Eye -> O)____|___ | ------________ | -------------- Field of vision Screen Let us pretend that the centre of the screen is the horizon of our little 3D world. If we draw a three dimensional line from object "o" to the centre of the eye, and place a pixel on the X and Y coordinates where it passes through the screen, we will notice that when we do the same with object o1, the pixel is closer to the horizon, even though their 3D X and Y coords are identical, but "o1"'s Z is larger then "o"'s. This means that the further away a point is, the closer to the horizon it is, or the smaller the object will appear. That sounds right, doesent it? But, I hear you cry, how do we translate this into a formula? The answer is quite simple. Divide your X and your Y by your Z. Think about it. The larger the number you divide by, the closer to zero, or the horizon, is the result! This means, the bigger the Z, the further away is the object! Here it is in equation form : nx := 256*x div (z-Zoff)+Xoff ny := 256*y div (z-Zoff)+Yoff NOTE : Zoff is how far away the entire object is, Xoff is the objects X value, and Yoff is the objects Y value. In the sample program, Xoff start off at 160 and Yoff starts off at 100, so that the object is in the middle of the screen. The 256 that you times by is the perspective with which you are viewing. Changing this value gives you a "fish eye" effect when viewing the object. Anyway, there you have it! Draw a pixel at nx,ny, and viola! you are now doing 3D! Easy, wasn't it? =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Possible improvements This program is not the most optimised routine you will ever encounter (;-)) ... it uses 12 muls and 2 divs per point. (Asphyxia currently has 9 muls and 2 divs per point) Real math is used for all the calculations in the sample program, which is slow, so fixed point math should be implemented (I will cover fixed point math in a future trainer). The line routine currently being used is very slow. Chain-4 could be used to cut down on screen flipping times. Color values per line should be added, base object morphing could be put in, polygons could be used instead of lines, handling of more then one object should be implemented, clipping should be added instead of not drawing something if any part of it is out of bounds. In other words, you have a lot of work ahead of you ;) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ In closing There are a lot of books out there on 3D, and quite a few sample programs too. Have a look at them, and use the best bits to create your own, unique 3D engine, with which you can do anything you want. I am very interested in 3D (though EzE and Goth wrote most of ASPHYXIA'S 3D routines), and would like to see what you can do with it. Leave me a message through one of the means described above. I am delving into the murky world of texture mapping. If anyone out there has some routines on the subject and are interested in swapping, give me a buzz! What to do in future trainers? Help me out on this one! Are there any effects/areas you would like a bit of info on? Leave me a message! I unfortunately did not get any messages regarding BBS's that carry this series, so the list that follows is the same one from last time. Give me your names, sysops! Aaaaargh!!! Try as I might, I can't think of a new quote. Next time, I promise! ;-) Bye for now, - Denthor These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical) ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ» ºBBS Name ºTelephone No. ºOpen ºMsgºFileºPastº ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹ ºASPHYXIA BBS #1 º(031) 765-5312 ºALL º * º * º * º ºASPHYXIA BBS #2 º(031) 765-6293 ºALL º * º * º * º ºConnectix BBS º(031) 266-9992 ºALL º * º º º ºFor Your Eyes Only BBS º(031) 285-318 ºA/H º * º * º * º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ Open = Open at all times or only A/H Msg = Available in message base File = Available in file base Past = Previous Parts available ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ TUTPROG8.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ {$X+} USES Crt; CONST VGA = $A000; MaxLines = 12; Obj : Array [1..MaxLines,1..2,1..3] of integer = ( ((-10,-10,-10),(10,-10,-10)),((-10,-10,-10),(-10,10,-10)), ((-10,10,-10),(10,10,-10)),((10,-10,-10),(10,10,-10)), ((-10,-10,10),(10,-10,10)),((-10,-10,10),(-10,10,10)), ((-10,10,10),(10,10,10)),((10,-10,10),(10,10,10)), ((-10,-10,10),(-10,-10,-10)),((-10,10,10),(-10,10,-10)), ((10,10,10),(10,10,-10)),((10,-10,10),(10,-10,-10)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the two ends of a line } Type Point = Record x,y,z:real; { The data on every point we rotate} END; Virtual = Array [1..64000] of byte; { The size of our Virtual Screen } VirtPtr = ^Virtual; { Pointer to the virtual screen } VAR Lines : Array [1..MaxLines,1..2] of Point; { The base object rotated } Translated : Array [1..MaxLines,1..2] of Point; { The rotated object } Xoff,Yoff,Zoff:Integer; { Used for movement of the object } lookup : Array [0..360,1..2] of real; { Our sin and cos lookup table } Virscr : VirtPtr; { Our first Virtual screen } Vaddr : word; { The segment of our virtual screen} {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetMCGA; { This procedure gets you into 320x200x256 mode. } BEGIN asm mov ax,0013h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetText; { This procedure returns you to text mode. } BEGIN asm mov ax,0003h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Cls (Where:word;Col : Byte); { This clears the screen to the specified color } BEGIN asm push es mov cx, 32000; mov es,[where] xor di,di mov al,[col] mov ah,al rep stosw pop es End; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetUpVirtual; { This sets up the memory needed for the virtual screen } BEGIN GetMem (VirScr,64000); vaddr := seg (virscr^); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure ShutDown; { This frees the memory used by the virtual screen } BEGIN FreeMem (VirScr,64000); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure flip(source,dest:Word); { This copies the entire screen at "source" to destination } begin asm push ds mov ax, [Dest] mov es, ax mov ax, [Source] mov ds, ax xor si, si xor di, di mov cx, 32000 rep movsw pop ds end; end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Pal(Col,R,G,B : Byte); { This sets the Red, Green and Blue values of a certain color } Begin asm mov dx,3c8h mov al,[col] out dx,al inc dx mov al,[r] out dx,al mov al,[g] out dx,al mov al,[b] out dx,al end; End; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Function rad (theta : real) : real; { This calculates the degrees of an angle } BEGIN rad := theta * pi / 180 END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetUpPoints; { This sets the basic offsets of the object, creates the lookup table and moves the object from a constant to a variable } VAR loop1:integer; BEGIN Xoff:=160; Yoff:=100; Zoff:=-256; For loop1:=0 to 360 do BEGIN lookup [loop1,1]:=sin (rad (loop1)); lookup [loop1,2]:=cos (rad (loop1)); END; For loop1:=1 to MaxLines do BEGIN Lines [loop1,1].x:=Obj [loop1,1,1]; Lines [loop1,1].y:=Obj [loop1,1,2]; Lines [loop1,1].z:=Obj [loop1,1,3]; Lines [loop1,2].x:=Obj [loop1,2,1]; Lines [loop1,2].y:=Obj [loop1,2,2]; Lines [loop1,2].z:=Obj [loop1,2,3]; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); { This puts a pixel on the screen by writing directly to memory. } BEGIN Asm mov ax,[where] mov es,ax mov bx,[X] mov dx,[Y] mov di,bx mov bx, dx {; bx = dx} shl dx, 8 shl bx, 6 add dx, bx {; dx = dx + bx (ie y*320)} add di, dx {; finalise location} mov al, [Col] stosb End; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Line(a,b,c,d:integer;col:byte;where:word); { This draws a solid line from a,b to c,d in colour col } function sgn(a:real):integer; begin if a>0 then sgn:=+1; if a<0 then sgn:=-1; if a=0 then sgn:=0; end; var i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer; begin u:= c - a; v:= d - b; d1x:= SGN(u); d1y:= SGN(v); d2x:= SGN(u); d2y:= 0; m:= ABS(u); n := ABS(v); IF NOT (M>N) then BEGIN d2x := 0 ; d2y := SGN(v); m := ABS(v); n := ABS(u); END; s := m shr 1; FOR i := 0 TO m DO BEGIN putpixel(a,b,col,where); s := s + n; IF not (s0 then BEGIN temp.x:=lookup[y,2]*translated[loop1,1].x - lookup[y,1]*translated[loop1,1].y; temp.y:=lookup[y,1]*translated[loop1,1].x + lookup[y,2]*translated[loop1,1].y; temp.z:=translated[loop1,1].z; translated[loop1,1]:=temp; END; If z>0 then BEGIN temp.x:=lookup[z,2]*translated[loop1,1].x + lookup[z,1]*translated[loop1,1].z; temp.y:=translated[loop1,1].y; temp.z:=-lookup[z,1]*translated[loop1,1].x + lookup[z,2]*translated[loop1,1].z; translated[loop1,1]:=temp; END; temp.x:=lines[loop1,2].x; temp.y:=cos (rad(X))*lines[loop1,2].y - sin (rad(X))*lines[loop1,2].z; temp.z:=sin (rad(X))*lines[loop1,2].y + cos (rad(X))*lines[loop1,2].z; translated[loop1,2]:=temp; If y>0 then BEGIN temp.x:=cos (rad(Y))*translated[loop1,2].x - sin (rad(Y))*translated[loop1,2].y; temp.y:=sin (rad(Y))*translated[loop1,2].x + cos (rad(Y))*translated[loop1,2].y; temp.z:=translated[loop1,2].z; translated[loop1,2]:=temp; END; If z>0 then BEGIN temp.x:=cos (rad(Z))*translated[loop1,2].x + sin (rad(Z))*translated[loop1,2].z; temp.y:=translated[loop1,2].y; temp.z:=-sin (rad(Z))*translated[loop1,2].x + cos (rad(Z))*translated[loop1,2].z; translated[loop1,2]:=temp; END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure DrawPoints; { This draws the translated object to the virtual screen } VAR loop1:Integer; nx,ny,nx2,ny2:integer; temp:integer; BEGIN For loop1:=1 to MaxLines do BEGIN If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) then BEGIN temp:=round (translated[loop1,1].z+zoff); nx :=round (256*translated[loop1,1].X) div temp+xoff; ny :=round (256*translated[loop1,1].Y) div temp+yoff; temp:=round (translated[loop1,2].z+zoff); nx2:=round (256*translated[loop1,2].X) div temp+xoff; ny2:=round (256*translated[loop1,2].Y) div temp+yoff; If (NX > 0) and (NX < 320) and (NY > 25) and (NY < 200) and (NX2> 0) and (NX2< 320) and (NY2> 25) and (NY2< 200) then line (nx,ny,nx2,ny2,13,vaddr); END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure ClearPoints; { This clears the translated object from the virtual screen ... believe it or not, this is faster then a straight "cls (vaddr,0)" } VAR loop1:Integer; nx,ny,nx2,ny2:Integer; temp:integer; BEGIN For loop1:=1 to MaxLines do BEGIN If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) then BEGIN temp:=round (translated[loop1,1].z+zoff); nx :=round (256*translated[loop1,1].X) div temp+xoff; ny :=round (256*translated[loop1,1].Y) div temp+yoff; temp:=round (translated[loop1,2].z+zoff); nx2:=round (256*translated[loop1,2].X) div temp+xoff; ny2:=round (256*translated[loop1,2].Y) div temp+yoff; If (NX > 0) and (NX < 320) and (NY > 25) and (NY < 200) and (NX2> 0) and (NX2< 320) and (NY2> 25) and (NY2< 200) then line (nx,ny,nx2,ny2,0,vaddr); END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure MoveAround; { This is the main display procedure. Firstly it brings the object towards the viewer by increasing the Zoff, then passes control to the user } VAR deg,loop1:integer; ch:char; BEGIN deg:=0; ch:=#0; Cls (vaddr,0); DrawLogo; For loop1:=-256 to -40 do BEGIN zoff:=loop1*2; RotatePoints (deg,deg,deg); DrawPoints; flip (vaddr,vga); ClearPoints; deg:=(deg+5) mod 360; END; Repeat if keypressed then BEGIN ch:=upcase (Readkey); Case ch of 'A' : zoff:=zoff+5; 'Z' : zoff:=zoff-5; ',' : xoff:=xoff-5; '.' : xoff:=xoff+5; 'S' : yoff:=yoff-5; 'X' : yoff:=yoff+5; END; END; DrawPoints; flip (vaddr,vga); ClearPoints; RotatePoints (deg,deg,deg); deg:=(deg+5) mod 360; Until ch=#27; END; BEGIN SetUpVirtual; Writeln ('Greetings and salutations! Hope you had a great Christmas and New'); Writeln ('year! ;-) ... Anyway, this tutorial is on 3-D, so this is what is'); Writeln ('going to happen ... a wireframe square will come towards you.'); Writeln ('When it gets close, you get control. "A" and "Z" control the Z'); Writeln ('movement, "," and "." control the X movement, and "S" and "X"'); Writeln ('control the Y movement. I have not included rotation control, but'); Writeln ('it should be easy enough to put in yourself ... if you have any'); Writeln ('hassles, leave me mail.'); Writeln; Writeln ('Read the main text file for ideas on improving this code ... and'); Writeln ('welcome to the world of 3-D!'); writeln; writeln; Write (' Hit any key to contine ...'); Readkey; SetMCGA; SetUpPoints; MoveAround; SetText; ShutDown; Writeln ('All done. This concludes the eigth sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the names of GRANT'); Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid'); Writeln ('Connectix BBS user, and occasionally read RSAProg.'); Writeln ('For discussion purposes, I am also the moderator of the Programming'); Writeln ('newsgroup on the For Your Eyes Only BBS.'); Writeln ('The numbers are available in the main text. You may also write to me at:'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 9 ]==-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Introduction Hi there! ASPHYXIA is BACK with our first MegaDemo, Psycho Neurosis! A paltry 1.3MB download is all it takes to see the group from Durbs first major production! We are quite proud of it, and think you should see it ;) Secondly, I released a small little trainer (a trainerette ;-)) on RsaPROG and Connexctix BBS mail, also on the ASPHYXIA BBS as COPPERS.ZIP It is a small Pascal program demonstrating how to display copper bars in text mode. Also includes a check for horizontal retrace (A lot of people wanted it, that is why I wrote the program) (ASPHYXIA ... first with the trainer goodies ;-) aargh, sorry, had to be done )) Thirdly, sorry about the problems with Tut 8! If you had all the checking on, the tutorial would probably die on the first points. The reason is this : in the first loop, we have DrawPoints then RotatePoints. The variables used in DrawPoints are set in RotatePoints, so if you put RotatePoints before DrawPoints, the program should work fine. Alternatively, turn off error checking 8-) Fourthly, I have had a surprisingly large number of people saying that "I get this, like, strange '286 instructions not enabled' message! What's wrong with your code, dude?" To all of you, get into Pascal, hit Alt-O (for options), hit enter and a 2 (for Enable 286 instructions). Hard hey? Doesn't anyone EVER set up their version of Pascal? Now, on to todays tutorial! 3D solids. That is what the people wanted, that is what the people get! This tutorial is mainly on how to draw the polygon on screen. For details on how the 3D stuff works, check out tut 8. If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail on the ASPHYXIA BBS. 2) Write to Denthor, EzE or Goth on Connectix. 3) Write to : Grant Smith P.O.Box 270 Kloof 3640 Natal 4) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity) 5) Write to mcphail@beastie.cs.und.ac.za on InterNet, and mention the word Denthor near the top of the letter. NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ How to draw a polygon Sounds easy enough, right? WRONG! There are many, many different ways to go about this, and today I'll only be showing you one. Please don't take what is written here as anything approaching the best method, it is just here to get you on your way... The procedure I will be using here is based on something most of us learned in standard eight ... I think. I seem to recall doing something like this in Mrs. Reids maths class all those years ago ;) Take two points, x1,y1 and x2,y2. Draw them : + (x1,y1) \ \ <-- Point a somewhere along the line \ + (x2,y2) Right, so what we have to do is this : if we know the y-coord of a, what is it's x-coord? To prove the method we will give the points random values. + (2,10) \ \ <-- a.y = 12 \ + (15,30) Right. Simple enough problem. This is how we do it : (a.y-y1) = (12 - 10) {to get a.y as though y1 was zero} *(x2-x1) = *(15 - 2) {the total x-length of the line} /(y2-y1) = /(30 - 10) {the total y-length of the line} +x1 = +2 { to get the equation back to real coords} So our equation is : (a.y-y1)*(x2-x1)/(y2-y1)+x4 or (12-10)*(15-2)/(30-10)+2 which gives you : 2*13/20+2 = 26/20+2 = 3.3 That means that along the line with y=12, x is equal to 3.3. Since we are not concerned with the decimal place, we replace the / with a div, which in Pascal gives us an integer result, and is faster too. All well and good, I hear you cry, but what does this have to do with life and how it relates to polygons in general. The answer is simple. For each of the four sides of the polygon we do the above test for each y line. We store the smallest and the largest x values into separate variables for each line, and draw a horizontal line between them. Ta-Dah! We have a cool polygon! For example : Two lines going down : + + / <-x1 x2->| <--For this y line / | + + Find x1 and x2 for that y, then draw a line between them. Repeat for all y values. Of course, it's not as simple as that. We have to make sure we only check those y lines that contain the polygon (a simple min y, max y test for all the points). We also have to check that the line we are calculating actually extends as far as where our current y is (check that the point is between both y's). We have to compare each x to see weather it is smaller then the minimum x value so far, or bigger then the maximum (the original x min is set as a high number, and the x max is set as a small number). We must also check that we only draw to the place that we can see ( 0-319 on the x ; 0-199 on the y (the size of the MCGA screen)) To see how this looks in practice, have a look at the sample code provided. (Mrs. Reid would probably kill me for the above explanation, so when you learn it in school, split it up into thousands of smaller equations to get the same answer ;)) Okay, that's it! What's that? How do you draw a vertical line? Thats simple ... =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Drawing a vertical line Right, this is a lot easier than drawing a normal line (Tut 5 .. I think), because you stay on the same y value. So, what you do is you set ES to the screen you want to write to, and get DI to the start of the y-line (see earlier trainers for a description of how SEGMENT:OFFSET works. IN : x1 , x2, y, color, where asm mov ax,where mov es,ax mov di,y mov ax,y shl di,8 { di:=di*256 } shl ax,6 { ax:=ax*64 } add di,ax { di := (y*256)+(y*64) := y*320 Faster then a straight multiplication } Right, now you add the first x value to get your startoff. add di,x1 Move the color to store into ah and al mov al,color mov ah,al { ah:=al:=color } then get CX equal to how many pixels across you want to go mov cx,x2 sub cx,x1 { cx:=x2-x1 } Okay, as we all know, moving a word is a lot faster then moving a byte, so we halve CX shr cx,1 { cx:=cx/2 } but what happens if CX was an odd number. After a shift, the value of the last number is placed in the carry flag, so what we do is jump over a single byte move if the carry flag is zero, or execute it if it is one. jnc @Start { If there is no carry, jump to label Start } stosb { ES:[DI]:=al ; increment DI } @Start : { Label Start } rep stosw { ES:[DI]:=ax ; DI:=DI+2; repeat CX times } Right, the finished product looks like this : Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler; { This draws a horizontal line from x1 to x2 on line y in color col } asm mov ax,where mov es,ax mov ax,y mov di,ax shl ax,8 shl di,6 add di,ax add di,x1 mov al,col mov ah,al mov cx,x2 sub cx,x1 shr cx,1 jnc @start stosb @Start : rep stosw end; Done! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ In closing This 3D system is still not perfect. It needs to be faster, and now I have also dumped the problem of face-sorting on you! Nyahahahaha! [ My sister and I were driving along the other day when she asked me, what would I like for my computer. I thought long and hard about it, and came up with the following hypothesis. When a girl gets a Barbie doll, she then wants the extra ballgown for the doll, then the hairbrush, and the car, and the house, and the friends etc. When a guy gets a computer, he wants the extra memory, the bigger hard drive, the maths co-pro, the better motherboard, the latest software, and the bigger monitor etc. I told my sister all of this, and finished up with : "So as you can see, computers are Barbie dolls for MEN!" She called me a chauvinist. And hit me. Hard. ] - Grant Smith 19:24 26/2/94 See you next time! - Denthor These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical) ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ» ºBBS Name ºTelephone No. ºOpen ºMsgºFileºPastº ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹ ºASPHYXIA BBS #1 º(031) 765-5312 ºALL º * º * º * º ºASPHYXIA BBS #2 º(031) 765-6293 ºALL º * º * º * º ºConnectix BBS º(031) 266-9992 ºALL º º * º * º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ Open = Open at all times or only A/H Msg = Available in message base File = Available in file base Past = Previous Parts available Does no other BBS's ANYWHERE carry the trainer? Am I writing this for three people who get it from one of these BBS's each week? Should I go on? (Hehehehe ... I was pleased to note that Tut 8 was THE most downloaded file from ASPHYXIA BBS last month ... ) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ TUTPROG9.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ {$X+} USES Crt; CONST VGA = $A000; maxpolys = 5; A : Array [1..maxpolys,1..4,1..3] of integer = ( ((-10,10,0),(-2,-10,0),(0,-10,0),(-5,10,0)), ((10,10,0),(2,-10,0),(0,-10,0),(5,10,0)), ((-2,-10,0),(2,-10,0),(2,-5,0),(-2,-5,0)), ((-6,0,0),(6,0,0),(7,5,0),(-7,5,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } S : Array [1..maxpolys,1..4,1..3] of integer = ( ((-10,-10,0),(10,-10,0),(10,-7,0),(-10,-7,0)), ((-10,10,0),(10,10,0),(10,7,0),(-10,7,0)), ((-10,1,0),(10,1,0),(10,-2,0),(-10,-2,0)), ((-10,-8,0),(-7,-8,0),(-7,0,0),(-10,0,0)), ((10,8,0),(7,8,0),(7,0,0),(10,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } P : Array [1..maxpolys,1..4,1..3] of integer = ( ((-10,-10,0),(-7,-10,0),(-7,10,0),(-10,10,0)), ((10,-10,0),(7,-10,0),(7,0,0),(10,0,0)), ((-9,-10,0),(9,-10,0),(9,-7,0),(-9,-7,0)), ((-9,-1,0),(9,-1,0),(9,2,0),(-9,2,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } H : Array [1..maxpolys,1..4,1..3] of integer = ( ((-10,-10,0),(-7,-10,0),(-7,10,0),(-10,10,0)), ((10,-10,0),(7,-10,0),(7,10,0),(10,10,0)), ((-9,-1,0),(9,-1,0),(9,2,0),(-9,2,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } Y : Array [1..maxpolys,1..4,1..3] of integer = ( ((-7,-10,0),(0,-3,0),(0,0,0),(-10,-7,0)), ((7,-10,0),(0,-3,0),(0,0,0),(10,-7,0)), ((-2,-3,0),(2,-3,0),(2,10,0),(-2,10,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } X : Array [1..maxpolys,1..4,1..3] of integer = ( ((-7,-10,0),(10,7,0),(7,10,0),(-10,-7,0)), ((7,-10,0),(-10,7,0),(-7,10,0),(10,-7,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } I : Array [1..maxpolys,1..4,1..3] of integer = ( ((-10,-10,0),(10,-10,0),(10,-7,0),(-10,-7,0)), ((-10,10,0),(10,10,0),(10,7,0),(-10,7,0)), ((-2,-9,0),(2,-9,0),(2,9,0),(-2,9,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)), ((0,0,0),(0,0,0),(0,0,0),(0,0,0)) ); { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), } { (X2,Y2,Z2) ... for the 4 points of a poly } Type Point = Record x,y,z:real; { The data on every point we rotate} END; Virtual = Array [1..64000] of byte; { The size of our Virtual Screen } VirtPtr = ^Virtual; { Pointer to the virtual screen } VAR Lines : Array [1..maxpolys,1..4] of Point; { The base object rotated } Translated : Array [1..maxpolys,1..4] of Point; { The rotated object } Xoff,Yoff,Zoff:Integer; { Used for movement of the object } lookup : Array [0..360,1..2] of real; { Our sin and cos lookup table } Virscr : VirtPtr; { Our first Virtual screen } Vaddr : word; { The segment of our virtual screen} {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetMCGA; { This procedure gets you into 320x200x256 mode. } BEGIN asm mov ax,0013h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetText; { This procedure returns you to text mode. } BEGIN asm mov ax,0003h int 10h end; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Cls (Where:word;Col : Byte); { This clears the screen to the specified color } BEGIN asm push es mov cx, 32000; mov es,[where] xor di,di mov al,[col] mov ah,al rep stosw pop es End; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetUpVirtual; { This sets up the memory needed for the virtual screen } BEGIN GetMem (VirScr,64000); vaddr := seg (virscr^); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure ShutDown; { This frees the memory used by the virtual screen } BEGIN FreeMem (VirScr,64000); END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} procedure flip(source,dest:Word); { This copies the entire screen at "source" to destination } begin asm push ds mov ax, [Dest] mov es, ax mov ax, [Source] mov ds, ax xor si, si xor di, di mov cx, 32000 rep movsw pop ds end; end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Pal(Col,R,G,B : Byte); { This sets the Red, Green and Blue values of a certain color } Begin asm mov dx,3c8h mov al,[col] out dx,al inc dx mov al,[r] out dx,al mov al,[g] out dx,al mov al,[b] out dx,al end; End; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler; { This draws a horizontal line from x1 to x2 on line y in color col } asm mov ax,where mov es,ax mov ax,y mov di,ax shl ax,8 shl di,6 add di,ax add di,x1 mov al,col mov ah,al mov cx,x2 sub cx,x1 shr cx,1 jnc @start stosb @Start : rep stosw end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure DrawPoly(x1,y1,x2,y2,x3,y3,x4,y4:integer;color:byte;where:word); { This draw a polygon with 4 points at x1,y1 , x2,y2 , x3,y3 , x4,y4 in color col } var x:integer; mny,mxy:integer; mnx,mxx,yc:integer; mul1,div1, mul2,div2, mul3,div3, mul4,div4:integer; begin mny:=y1; mxy:=y1; if y2mxy then mxy:=y2; if y3mxy then mxy:=y3; { Choose the min y mny and max y mxy } if y4mxy then mxy:=y4; if mny<0 then mny:=0; if mxy>199 then mxy:=199; if mny>199 then exit; if mxy<0 then exit; { Verticle range checking } mul1:=x1-x4; div1:=y1-y4; mul2:=x2-x1; div2:=y2-y1; mul3:=x3-x2; div3:=y3-y2; mul4:=x4-x3; div4:=y4-y3; { Constansts needed for intersection calc } for yc:=mny to mxy do begin mnx:=320; mxx:=-1; if (y4>=yc) or (y1>=yc) then if (y4<=yc) or (y1<=yc) then { Check that yc is between y1 and y4 } if not(y4=y1) then begin x:=(yc-y4)*mul1 div div1+x4; { Point of intersection on x axis } if xmxx then mxx:=x; { Set point as start or end of horiz line } end; if (y1>=yc) or (y2>=yc) then if (y1<=yc) or (y2<=yc) then { Check that yc is between y1 and y2 } if not(y1=y2) then begin x:=(yc-y1)*mul2 div div2+x1; { Point of intersection on x axis } if xmxx then mxx:=x; { Set point as start or end of horiz line } end; if (y2>=yc) or (y3>=yc) then if (y2<=yc) or (y3<=yc) then { Check that yc is between y2 and y3 } if not(y2=y3) then begin x:=(yc-y2)*mul3 div div3+x2; { Point of intersection on x axis } if xmxx then mxx:=x; { Set point as start or end of horiz line } end; if (y3>=yc) or (y4>=yc) then if (y3<=yc) or (y4<=yc) then { Check that yc is between y3 and y4 } if not(y3=y4) then begin x:=(yc-y3)*mul4 div div4+x3; { Point of intersection on x axis } if xmxx then mxx:=x; { Set point as start or end of horiz line } end; if mnx<0 then mnx:=0; if mxx>319 then mxx:=319; { Range checking on horizontal line } if mnx<=mxx then hline (mnx,mxx,yc,color,where); { Draw the horizontal line } end; end; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Function rad (theta : real) : real; { This calculates the degrees of an angle } BEGIN rad := theta * pi / 180 END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure SetUpPoints; { This creates the lookup table } VAR loop1,loop2:integer; BEGIN For loop1:=0 to 360 do BEGIN lookup [loop1,1]:=sin (rad (loop1)); lookup [loop1,2]:=cos (rad (loop1)); END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); { This puts a pixel on the screen by writing directly to memory. } BEGIN Asm mov ax,[where] mov es,ax mov bx,[X] mov dx,[Y] mov di,bx mov bx, dx {; bx = dx} shl dx, 8 shl bx, 6 add dx, bx {; dx = dx + bx (ie y*320)} add di, dx {; finalise location} mov al, [Col] stosb End; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure RotatePoints (X,Y,Z:Integer); { This rotates object lines by X,Y and Z; then places the result in TRANSLATED } VAR loop1,loop2:integer; temp:point; BEGIN For loop1:=1 to maxpolys do BEGIN For loop2:=1 to 4 do BEGIN temp.x:=lines[loop1,loop2].x; temp.y:=lookup[x,2]*lines[loop1,loop2].y - lookup[x,1]*lines[loop1,loop2].z; temp.z:=lookup[x,1]*lines[loop1,loop2].y + lookup[x,2]*lines[loop1,loop2].z; translated[loop1,loop2]:=temp; If y>0 then BEGIN temp.x:=lookup[y,2]*translated[loop1,loop2].x - lookup[y,1]*translated[loop1,loop2].y; temp.y:=lookup[y,1]*translated[loop1,loop2].x + lookup[y,2]*translated[loop1,loop2].y; temp.z:=translated[loop1,loop2].z; translated[loop1,loop2]:=temp; END; If z>0 then BEGIN temp.x:=lookup[z,2]*translated[loop1,loop2].x + lookup[z,1]*translated[loop1,loop2].z; temp.y:=translated[loop1,loop2].y; temp.z:=-lookup[z,1]*translated[loop1,loop2].x + lookup[z,2]*translated[loop1,loop2].z; translated[loop1,loop2]:=temp; END; END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure DrawPoints; { This draws the translated object to the virtual screen } VAR loop1:Integer; nx,ny,nx2,ny2,nx3,ny3,nx4,ny4:integer; temp:integer; BEGIN For loop1:=1 to maxpolys do BEGIN If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0) and (translated[loop1,3].z+zoff<0) and (translated[loop1,4].z+zoff<0) then BEGIN temp:=round (translated[loop1,1].z+zoff); nx :=round (256*translated[loop1,1].X) div temp+xoff; ny :=round (256*translated[loop1,1].Y) div temp+yoff; temp:=round (translated[loop1,2].z+zoff); nx2:=round (256*translated[loop1,2].X) div temp+xoff; ny2:=round (256*translated[loop1,2].Y) div temp+yoff; temp:=round (translated[loop1,3].z+zoff); nx3:=round (256*translated[loop1,3].X) div temp+xoff; ny3:=round (256*translated[loop1,3].Y) div temp+yoff; temp:=round (translated[loop1,4].z+zoff); nx4:=round (256*translated[loop1,4].X) div temp+xoff; ny4:=round (256*translated[loop1,4].Y) div temp+yoff; drawpoly (nx,ny,nx2,ny2,nx3,ny3,nx4,ny4,13,vaddr); END; END; END; {ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ} Procedure MoveAround; { This is the main display procedure. Firstly it brings the object towards the viewer by increasing the Zoff, then passes control to the user } VAR deg,loop1,loop2:integer; ch:char; Procedure Whizz (sub:boolean); VAR loop1:integer; BEGIN For loop1:=-64 to -5 do BEGIN zoff:=loop1*8; if sub then xoff:=xoff-7 else xoff:=xoff+7; RotatePoints (deg,deg,deg); DrawPoints; flip (vaddr,vga); Cls (vaddr,0); deg:=(deg+5) mod 360; END; END; BEGIN deg:=0; ch:=#0; Yoff:=100; Xoff:=350; Cls (vaddr,0); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=a [loop1,loop2,1]; Lines [loop1,loop2].y:=a [loop1,loop2,2]; Lines [loop1,loop2].z:=a [loop1,loop2,3]; END; Whizz (TRUE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=s [loop1,loop2,1]; Lines [loop1,loop2].y:=s [loop1,loop2,2]; Lines [loop1,loop2].z:=s [loop1,loop2,3]; END; Whizz (FALSE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=p [loop1,loop2,1]; Lines [loop1,loop2].y:=p [loop1,loop2,2]; Lines [loop1,loop2].z:=p [loop1,loop2,3]; END; Whizz (TRUE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=h [loop1,loop2,1]; Lines [loop1,loop2].y:=h [loop1,loop2,2]; Lines [loop1,loop2].z:=h [loop1,loop2,3]; END; Whizz (FALSE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=y [loop1,loop2,1]; Lines [loop1,loop2].y:=y [loop1,loop2,2]; Lines [loop1,loop2].z:=y [loop1,loop2,3]; END; Whizz (TRUE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=x [loop1,loop2,1]; Lines [loop1,loop2].y:=x [loop1,loop2,2]; Lines [loop1,loop2].z:=x [loop1,loop2,3]; END; Whizz (FALSE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=i [loop1,loop2,1]; Lines [loop1,loop2].y:=i [loop1,loop2,2]; Lines [loop1,loop2].z:=i [loop1,loop2,3]; END; Whizz (TRUE); For loop1:=1 to maxpolys do For loop2:=1 to 4 do BEGIN Lines [loop1,loop2].x:=a [loop1,loop2,1]; Lines [loop1,loop2].y:=a [loop1,loop2,2]; Lines [loop1,loop2].z:=a [loop1,loop2,3]; END; Whizz (FALSE); cls (vaddr,0); cls (vga,0); Xoff := 160; Repeat if keypressed then BEGIN ch:=upcase (Readkey); Case ch of 'A' : zoff:=zoff+5; 'Z' : zoff:=zoff-5; ',' : xoff:=xoff-5; '.' : xoff:=xoff+5; 'S' : yoff:=yoff-5; 'X' : yoff:=yoff+5; END; END; DrawPoints; flip (vaddr,vga); cls (vaddr,0); RotatePoints (deg,deg,deg); deg:=(deg+5) mod 360; Until ch=#27; END; BEGIN SetUpVirtual; clrscr; Writeln ('Hello there! Varsity has begun once again, so it is once again'); Writeln ('back to the grindstone ;-) ... anyway, this tutorial is, by'); Writeln ('popular demand, on poly-filling, in relation to 3-D solids.'); Writeln; Writeln ('In this program, the letters of ASPHYXIA will fly past you. As you'); Writeln ('will see, they are solid, not wireframe. After the last letter has'); Writeln ('flown by, a large A will be left in the middle of the screen.'); Writeln; Writeln ('You will be able to move it around the screen, and you will notice'); Writeln ('that it may have bits only half on the screen, i.e. clipping is'); Writeln ('perfomed. To control it use the following : "A" and "Z" control the Z'); Writeln ('movement, "," and "." control the X movement, and "S" and "X"'); Writeln ('control the Y movement. I have not included rotation control, but'); Writeln ('it should be easy enough to put in yourself ... if you have any'); Writeln ('hassles, leave me mail.'); Writeln; Writeln ('I hope this is what you wanted...leave me mail for new ideas.'); writeln; writeln; Write (' Hit any key to contine ...'); Readkey; SetMCGA; SetUpPoints; MoveAround; SetText; ShutDown; Writeln ('All done. This concludes the ninth sample program in the ASPHYXIA'); Writeln ('Training series. You may reach DENTHOR under the names of GRANT'); Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS. I am also an avid'); Writeln ('Connectix BBS user, and occasionally read RSAProg.'); Writeln ('The numbers are available in the main text. You may also write to me at:'); Writeln (' Grant Smith'); Writeln (' P.O. Box 270'); Writeln (' Kloof'); Writeln (' 3640'); Writeln ('I hope to hear from you soon!'); Writeln; Writeln; Write ('Hit any key to exit ...'); Readkey; END. ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸ ³ W E L C O M E ³ ³ To the VGA Trainer Program ³ ³ ³ By ³ ³ ³ DENTHOR of ASPHYXIA ³ ³ ³ ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ --==[ PART 10 ]==-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Introduction Wow! The trainer has finally reached part 10! This will also be the first part introduced simultaneously to local BBS's and the INTERNET at the same time! Yes folks, I put up a copy of previous tutorials onto various ftp sites, and awaited the flames saying that the net.gurus already knew this stuff, and why was I wasting disk space! The flames did not appear (well, except for one), and I got some messages saying keep it up, so from now on I will upload all future trainers to ftp sites too (wasp.eng.ufl.edu , cs.uwp.edu etc.). I will also leave a notice in the USENET groups comp.lang.pascal and comp.sys.ibm.pc.demos when a new part is finished (Until enough people say stop ;-)) I can also be reached at my new E-Mail address, smith9@batis.bis.und.ac.za Well, this tutorial is on Chain-4. When asked to do a trainer on Chain-4, I felt that I would be walking on much travelled ground (I have seen numerous trainers on the subject), but the people who asked me said that they hadn't seen any, so could I do one anyway? Who am I to say no? The sample program attached isn't that great, but I am sure that all you people out there can immediately see the potential that Chain-4 holds. If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail on the ASPHYXIA BBS. 2) Write to Denthor, EzE or Goth on Connectix. 3) Write to : Grant Smith P.O.Box 270 Kloof 3640 Natal South Africa 4) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity). Call +27-31-73-2129 if you call from outside South Africa. (It's YOUR phone bill ;-)) 5) Write to smith9@batis.bis.und.ac.za in E-Mail. NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ What is Chain-4? You people out there all have at least 256k vga cards. Most of you have 512k vga cards, and some have 1MB vga cards. But what you see on your screen, as discussed in previous trainers, is 64k of data! What happened to the other 192k??? Chain-4 is a method of using all 256k at one time. The way this is done is simple. 1 screen = 64k. 64k * 4 = 256k. Therefore, chain-4 allows you to write to four screens, while displaying one of them. You can then move around these four screens to see the data on them. Think of the Chain-4 screen as a big canvas. The viewport, the bit you see out of, is a smaller rectangle which can be anywhere over the bigger canvas. +----------------------------+ Chain-4 screen | +--+ | | | | <- Viewport | | +--+ | | | +----------------------------+ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ The size of the chain-4 screen The Chain-4 screen, can be any size that adds up to 4 screens. For example, it can be 4 screens across and one screen down, or one screen across and 4 screens down, or two screens across and two screens down, and any size in between. In the sample program, the size is a constant. The size * 8 is how many pixels across there are on the chain-4 screen, ie Size = 40 = 320 pixels across = 1 screen across, 4 screens down Size = 80 = 640 pixels across = 2 screens across, 2 screens down etc. We need to know the size of the screen for almost all dealings with the Chain-4 screen, for obvious reasons. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Layout of the chain-4 screen, and accessing it If you will remember all the way back to Part 1 of this series, I explained that the memory layout of the MCGA screen is linear. Ie, the top left hand pixel was pixel zero, the one to the right of it was number one, the next one was number two etc. With Chain-4, things are very different. Chain-4 gets the 4 screens and chains them together (hence the name :)). Each screen has a different plane value, and must be accessed differently. The reason for this is that a segment of memory is only 64k big, so that we could not fit the entire Chain-4 screen into one segment. All Chain-4 screens are accessed from $a000, just like in MCGA mode. What we do is, before we write to the screen, find out what plane we are writing to, set that plane, then plot the pixel. Here is how we find out how far in to plot the pixel and what plane it is on : Instead of the linear model of MCGA mode, ie : ÚÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄ¿ ³00³01³02³03³04³05³06³07³08³09³10³11³ ... Each plane of the Chain-4 screen accesses the memory in this way : Plane 0 : ÚÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄ¿ ³00³ ³ ³ ³01³ ³ ³ ³02³ ³ ³ ³ ... Plane 1 : ÚÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄ¿ ³ ³00³ ³ ³ ³01³ ³ ³ ³02³ ³ ³ ... Plane 2 : ÚÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄ¿ ³ ³ ³00³ ³ ³ ³01³ ³ ³ ³02³ ³ ... Plane 3 : ÚÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄ¿ ³ ³ ³ ³00³ ³ ³ ³01³ ³ ³ ³02³ ... In this way, by choosing the right plane to write to, we can access all of the 256k of memory available to us. The plane that we write to can easily be found by the simple calculation of x mod 4, and the x coordinate is also found by x div 4. We work out our y by multiplying it by the size of our chain-4 screen. NOTE : It is possible to write to all four planes at once by setting the correct port values. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ Uses of Chain-4 The uses of Chain-4 are many. One could write data to one screen, then flip to it (the move_to command is almost instantaneous). This means that 64k of memory does not need to be set aside for a virtual screen, you are using the vga cards memory instead! Scrolling is much easier to code for in Chain-4 mode. It is possible to "tweak" the mode into other resolutions. In our demo, our vectors were in 320x240 mode, and our dot vectors were in 320x400 mode. The main disadvantage of chain-4 as I see it is the plane swapping, which can be slow. With a bit of clever coding however, these can be kept down to a minimum. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ The sample programs The first sample program is GFX.PAS. This is a until in which I have placed most of our routines from previous tuts. All the procedures and variables you can see under the INTERFACE section can be used in any program with GFX in the USES clause. In other words, I could do this : USES GFX,crt; BEGIN Setupvirtual; cls (vaddr,0); Shutdown; END. This program would compile perfectly. What I suggest you do is this : Rename the file to a name that suites you (eg your group name), change the first line of the unit to that name, then add all useful procedures etc. to the unit. Make it grow :-). The second file is the sample program (note the USES GFX,crt; up near the top!). The program is easy to understand and is documented. The bit that I want to draw your attention to is the constant, BIT. Because I am distributing this file to many places in text form, not binary form, I could not just add a .CEL file with the program. So what I did was write some text in one color then saved it as a .CEL . I then wrote a ten line program that did the following : Moving from left to right, it counted how many pixels were of color zero, then saved the byte value to an array. When it came across color one, is counted for how long that went on then saved the byte value and saved it to an array and so on. When it was finished, I converted the array into a text file in the CONST format. Not too cunning, but I thought I had better explain it ;-) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= þ In closing There are other documents and sample programs available on Chain-4 and it's like : Try XLIB for one... Finally! Some BBS's have joined my BBS list! (Okay, only two new ones, but it's a start ;-)) All you international BBS's! If you will regularly download the tuts from an FTP site, give me your names! I own a car. The car's name is Bob. A few days ago, Bob was in an accident, and now has major damage to his front. Knowing insurance, I probably won't get much, probably nothing (the other guy wasn't insured, and I am only 18 :( ). I will probably have to find work in order to pay for my repairs. The point to this meandering is this : I am upset, so if you think you are getting a quote, you can just forget it. Oh, well. Life goes on! See you next time, - Denthor These fine BBS's carry the ASPHYXIA DEMO TRAINER SERIES : (alphabetical) ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ» ºBBS Name ºTelephone No. ºOpen ºMsgºFileºPastº ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹ ºASPHYXIA BBS #1 º(031) 765-5312 ºALL º * º * º * º ºASPHYXIA BBS #2 º(031) 765-6293 ºALL º * º * º * º ºConnectix BBS º(031) 266-9992 ºALL º º * º * º ºPOP! º(012) 661-1257 ºALL º º * º * º ºPure Surf BBS º(031) 561-5943 ºA/H º º * º * º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ For international users : If you live outside the Republic of South Africa, do the following : Dial +27, dont dial the first 0, but dial the rest of the number. Eg, for the ASPHYXIA BBS : +27-31-765-5312 Open = Open at all times or only A/H Msg = Available in message base File = Available in file base Past = Previous Parts available ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ C4TUT.PAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ (* Well folks, here it is - the long awaited for Chain-4 trainer. The routines are commented so I'm not going to say too much more here, except a few things. 1: If ya don't understand this (not suprising its bloody cryptic!) then if ur serious go out and buy - Programming the EGA & VGA Cards I don't know who the book is by, so don't ask. Perhaps you know Greg? 2: The code is unoptimised. I wrote it specifically for this conf. and I'm buggered if I'm gonna give out my wholely (sp? ahh stuff it :-)) optimised code. If you want it faster, OPTIMISE IT!! HINT: Its faster to load ax, with a low byte/high byte combination and out a word instead of a byte at a time. If u don't know what I'm talking about, too bad :-) 3: If you use/like/whatever this code, please give Asphyxia a mention. It wos bloody hard work figuring out how all this cr*p works, we couldn't have done it with out a little guidence (thanx Gregie Poo). 4: LiveWire got interested in the whole tut/trainer idea and MAY be putting together a doc on how the whole thing works, including Pel-Panning which I haven't included here. 5: Good luck with the code, and if you write anything with it, I'd appreciate having a look at it :-). Feel free to direct any comments about the code to me in this conf. Or at one of the contact addresses given in the code. l8rs EzE / Asphyxia --------------------------------=[ Cut Here ]=------------------------- *) {$X+,G+} Program Chain4_Tut; Uses Crt; Const Size : Byte = 80; Var Loop : Integer; Procedure Init_C4; Assembler; Asm mov ax, 0013h int 10h { set up bios initially for 13h } mov dx, 03c4h { Sequencer Address Register } mov al, 4 { Index 4 - Memory mode } out dx, al { select it. } inc dx { 03c5h - here we set the mem mode. } in al, dx { get whats already inside the reg } and al, 11110111b { un-set 4th bit - chain4 } out dx, al mov dx, 3d4h mov al, 13h { Offset Register - allocates amt. mem for } out dx, al { 1 displayable line as - length div 8, so } inc dx { we use 80 (80*8) = 640 = 2 pages across } mov al, [Size] { and cause of chain-4 i.e. 256k display } out dx, al { mem, 2 pages down for four pages } { NOTE: setting AL above to 40 selects 1 } { page across and four down (nice for } { 1942 type scrolling games) and setting } { AL to 160 selects 4 pages across and 1 } { down, nice for horizontal scrolling } End; Procedure Cls_C4; Assembler; Asm mov dx, 03c4h { 03c4h } mov al, 2 { Map Mask Register } out dx, al inc dx mov al, 00001111b { Select all planes to write to } out dx, al { Doing this to clear all planes at once } mov ax, 0a000h mov es, ax xor di, di { set es:di = Screen Mem } mov ax, 0000h { colour to put = black } mov cx, 32768 { 32768 (words) *2 = 65536 bytes - vga mem } cld rep stosw { clear it } End; Procedure PutPixel_C4(X, Y : Integer; Col : Byte); Assembler; Asm mov ax, [Y] { Y val multiplied by... } xor bx, bx mov bl, [Size] { Size.... } shl bx, 1 { *2 - just 'cause! (I can't remember why!)} mul bx mov bx, ax mov ax, [X] mov cx, ax shr ax, 2 add bx, ax { add X val div 4 (four planes) } and cx, 00000011b { clever way of finding x mod 4, i.e. } mov dx, 03c4h { which plane we're in. } mov al, 2 { then use 03c4h index 2 - write plane sel.} out dx, al { to set plane to write to. } mov al, 1 { plane to write to = 1 shl (X mod 4) } shl al, cl inc dx out dx, al mov ax, 0a000h mov es, ax mov al, [Col] mov es: [bx], al { then write pixel. } End; Function GetPixel_C4(X, Y : Integer): Byte; Assembler; Asm mov ax, [Y] { Y val multiplied by... } xor bx, bx mov bl, [Size] { Size.... } shl bx, 1 { *2 - just 'cause! (I can't remember why!)} mul bx mov bx, ax mov ax, [X] mov cx, ax shr ax, 2 add bx, ax { add X val div 4 (four planes) } and cx, 00000011b { clever way of finding x mod 4, i.e. } mov dx, 03c4h { which plane we're in. } mov al, 4h { then use 03c4h index 4 - read plane sel. } out dx, al { to set plane to read from. } mov al, cl { Plane to read from = X mod 4 } inc dx out dx, al mov ax, 0a000h mov es, ax mov al, es: [bx] { then return pixel read } End; Procedure MoveScr_C4(X,Y : Integer); Assembler; Asm mov ax, [Y] { Y val multiplied by... } xor bx, bx mov bl, [Size] { Size.... } shl bx, 1 { *2 - just 'cause! (I can't remember why!)} mul bx mov bx, ax add bx, [X] { Add X val } mov dx, 03d4h mov al, 0ch { CRTC address reg. } out dx, al { Start Address High Reg. } inc dx mov al, bh { send high byte of start address. } out dx, al dec dx mov al, 0dh { Start Address Low Reg. } out dx, al inc dx mov al, bl { send low byte of start address. } out dx, al End; Procedure SetText; Assembler; Asm mov ax, 0003h int 10h End; Procedure Creds; Begin SetText; While KeyPressed do ReadKey; Asm mov ah, 1 mov ch, 1 mov cl, 0 int 10h End; WriteLn('Chain-4 Trainer...'); WriteLn('By EzE of Asphyxia.'); WriteLn; WriteLn('Contact Us on ...'); WriteLn; WriteLn; WriteLn('the Asphyxia BBS (031) - 7655312'); WriteLn; WriteLn('Email : eze@'); WriteLn(' asphyxia@'); WriteLn(' edwards@'); WriteLn(' bailey@'); WriteLn(' mcphail@'); WriteLn(' beastie.cs.und.ac.za'); WriteLn; WriteLn('or peter.edwards@datavert.co.za'); WriteLn; WriteLn('Write me snail-mail at...'); WriteLn('P.O. Box 2313'); WriteLn('Hillcrest'); WriteLn('Natal'); WriteLn('3650'); Asm mov ah, 1 mov ch, 1 mov cl, 0 int 10h End; End; Begin Init_C4; Cls_C4; Repeat Putpixel_C4(Random(320),Random(200),Random(256)+1); Until KeyPressed; For Loop := 0 to 80 do begin MoveScr_C4(0,Loop); Delay(10); End; ReadKey; Loop := GetPixel_C4(100,100); Creds; WriteLn('Colour at location X:100, Y:100 was: ',Loop); End. --------------------------------=[ Cut Here ]=------------------------- ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INTRODUCTION TO MODE X ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ By Robert Schmidt ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ XINTRO18.TXT ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Title: INTRODUCTION TO MODE X Version: 1.8 Author: Robert Schmidt Copyright: (C) 1993 of Ztiff Zox Softwear - refer to Status below. Last revision: 25-Nov-93 (Modified for the PCGPE 17-Apr-94) Figures: 1. M13ORG - memory organization in mode 13h 2. MXORG - memory organization in unchained modes (Both files are appended to the end of this document) The figures are available as 7-bit ASCII text (ASC) files. Status: This article, its associated figures and source listings named above, are all donated to the public domain. Do with it whatever you like, but give credit where credit is due. The standard disclaimer applies. Index: 0. ABSTRACT 1. INTRODUCTION TO THE VGA AND ITS 256-COLOR MODE 2. GETTING MORE PAGES AND PUTTING YOUR FIRST PIXEL 3. THE ROAD FROM HERE 4. BOOKS ON THE SUBJECT 5. BYE - FOR NOW 0. ABSTRACT This text gives a fairly basic, yet technical, explanation to what, why and how Mode X is. It first tries to explain the layout of the VGA memory and the shortcomings of the standard 320x200 256-color mode, then gives instructions on how one can progress from mode 13h to a multipage, planar 320x200 256-color mode, and from there to the quasi-standard 320x240 mode, known as Mode X. A little experience in programming the standard VGA mode 13h (320x200 in 256 colors) is assumed. Likewise a good understanding of hexadecimal notation and the concepts of segments and I/O ports is assumed. Keep a VGA reference handy, which at least should have definitions of the VGA registers at bit level. Throughout the article, a simple graphics library for unchained (planar) 256-color modes is developed. The library supports the 320x200 and 320x240 modes, active and visible pages, and writing and reading individual pixels. 1. INTRODUCTION TO THE VGA AND ITS 256-COLOR MODE Since its first appearance on the motherboards of the IBM PS/2 50, 60 and 80 models in 1987, the Video Graphics Array has been the de facto standard piece of graphics hardware for IBM and compatible personal computers. The abbreviation, VGA, was to most people synonymous with acceptable resolution (640x480 pixels), and a stunning rainbow of colors (256 from a palette of 262,144), at least compared to the rather gory CGA and EGA cards. Sadly, to use 256 colors, the VGA BIOS limited the users to 320x200 pixels, i.e. the well-known mode 13h. This mode has one good and one bad asset. The good one is that each one of the 64,000 pixels is easily addressable in the 64 Kb video memory segment at 0A000h. Simply calculate the offset using this formula: offset = (y * 320) + x; Set the byte at this address (0A000h:offset) to the color you want, and the pixel is there. Reading a pixel is just as simple: just read the corresponding byte. This was heaven, compared to the havoc of planes and masking registers needed in 16-color modes. Suddenly, the distance from a graphics algorithm on paper to an implemented graphics routine in assembly was cut down to a fraction. The results were impressively fast, too! The bad asset is that mode 13h is also limited to only one page, i.e. the VGA can hold only one screenful at any one time (plus 1536 pixels, or about four lines). Most 16-color modes let the VGA hold more than one page, and this enables you to show one of the pages to the user, while drawing on another page in the meantime. Page flipping is an important concept in making flicker free animations. Nice looking and smooth scrolling is also almost impossible in mode 13h using plain VGA hardware. Now, the alert reader might say: "Hold on a minute! If mode 13h enables only one page, this means that there is memory for only one page. But I know for a fact that all VGAs have at least 256 Kb RAM, and one 320x200 256-color page should consume only 320*200=64000 bytes, which is less than 64 Kb. A standard VGA should room a little more than four 320x200 pages!" Quite correct, and to see how the BIOS puts this limitation on mode 13h, I'll elaborate a little on the memory organization of the VGA. The memory is separated into four bit planes. The reason for this stems from the EGA, where graphics modes were 16-color. Using bit planes, the designers chose to let each pixel on screen be addressable by a single bit in a single byte in the video segment. Assuming the palette has not been modified from the default, each plane represent one of the EGA primary colors: red, green, blue and intensity. When modifying the bit representing a pixel, the Write Plane Enable register is set to the wanted color. Reading is more complex and slower, since you can only read from a single plane at a time, by setting the Read Plane Select register. Now, since each address in the video segment can access 8 pixels, and there are 64 Kb addresses, 8 * 65,536 = 524,288 16-color pixels can be accessed. In a 320x200 16-color mode, this makes for about 8 (524,288/(320*200)) pages, in 640x480 you get nearly 2 (524,288/(640*480)) pages. In a 256-color mode, the picture changes subtly. The designers decided to fix the number of bit planes to 4, so extending the logic above to 8 planes and 256 colors does not work. Instead, one of their goals was to make the 256-color mode as easily accessible as possible. Comparing the 8 pixels/address in 16-color modes to the 1-to-1 correspondence of pixels and addresses of mode 13h, one can say that they have succeeded, but at a certain cost. For reasons I am not aware of, the designers came up with the following effective, but memory-wasting scheme: The address space of mode 13h is divided evenly across the four bit planes. When an 8-bit color value is written to a 16-bit address in the VGA segment, a bit plane is automatically selected by the 2 least significant bits of the address. Then all 8 bits of the data is written to the byte at the 16-bit address in the selected bitplane (have a look at figure 1). Reading works exactly the same way. Since the bit planes are so closely tied to the address, only every fourth byte in the video memory is accessible, and 192 Kb of a 256 Kb VGA go to waste. Eliminating the need to bother about planes sure is convenient and beneficial, but to most people the loss of 3/4 of the total VGA memory sounds just hilarious. To accomodate this new method of accessing video memory, the VGA designers introduced a new configuration bit called Chain-4, which resides as bit number 3 in index 4 of the Sequencer. In 16-color modes, the default state for this bit is off (zero), and the VGA operates as described earlier. In the VGA's standard 256-color mode, mode 13h, this bit is turned on (set to one), and this turns the tieing of bit planes and memory address on. In this state, the bit planes are said to be chained together, thus mode 13h is often called a _chained mode_. Note that Chain-4 in itself is not enough to set a 256-color mode - there are other registers which deals with the other subtle changes in nature from 16 to 256 colors. But, as we now will base our work with mode X on mode 13h, which already is 256-color, we won't bother about these for now. 2. GETTING MORE PAGES AND PUTTING YOUR FIRST PIXEL The observant reader might at this time suggest that clearing the Chain-4 bit after setting mode 13h will give us access to all 256 Kb of video memory, as the two least significant bits of the byte address won't be `wasted' on selecting a bit plane. This is correct. You might also start feeling a little uneasy, because something tells you that you'll instantly loose the simple addressing scheme of mode 13h. Sadly, that is also correct. At the moment Chain-4 is cleared, each byte offset addresses *four* sequential pixels, corresponding to the four planes addressed in 16-color modes. Every fourth pixel belong in the same plane. Before writing to a byte offset in the video segment, you should make sure that the 4-bit mask in the Write Plane Enable register is set correctly, according to which of the four addressable pixels you want to modify. In essence, it works like a 16-color mode with a twist. See figure 2. So, is this mode X? Not quite. We need to elaborate to the VGA how to fetch data for refreshing the monitor image. Explaining the logic behind this is beyond the scope of this getting-you-started text, and it wouldn't be very interesting anyway. Also, mode 13h has only 200 lines, while I promised 240 lines. I'll fix that later below. Here is the minimum snippet of code to initiate the 4 page variant of mode 13h (320x200), written in plain C, using some DOS specific features (see header for a note about the sources included): ----8<-------cut begin------ /* width and height should specify the mode dimensions. widthBytes specify the width of a line in addressable bytes. */ int width, height, widthBytes; /* actStart specifies the start of the page being accessed by drawing operations. visStart specifies the contents of the Screen Start register, i.e. the start of the visible page */ unsigned actStart, visStart; /* * set320x200x256_X() * sets mode 13h, then turns it into an unchained (planar), 4-page * 320x200x256 mode. */ set320x200x256_X() { union REGS r; /* Set VGA BIOS mode 13h: */ r.x.ax = 0x0013; int86(0x10, &r, &r); /* Turn off the Chain-4 bit (bit 3 at index 4, port 0x3c4): */ outport(SEQU_ADDR, 0x0604); /* Turn off word mode, by setting the Mode Control register of the CRT Controller (index 0x17, port 0x3d4): */ outport(CRTC_ADDR, 0xE317); /* Turn off doubleword mode, by setting the Underline Location register (index 0x14, port 0x3d4): */ outport(CRTC_ADDR, 0x0014); /* Clear entire video memory, by selecting all four planes, then writing 0 to the entire segment. */ outport(SEQU_ADDR, 0x0F02); memset(vga+1, 0, 0xffff); /* stupid size_t exactly 1 too small */ vga[0] = 0; /* Update the global variables to reflect the dimensions of this mode. This is needed by most future drawing operations. */ width = 320; height = 200; /* Each byte addresses four pixels, so the width of a scan line in *bytes* is one fourth of the number of pixels on a line. */ widthBytes = width / 4; /* By default we want screen refreshing and drawing operations to be based at offset 0 in the video segment. */ actStart = visStart = 0; } ----8<-------cut end------ As you can see, I've already provided some of the mechanics needed to support multiple pages, by providing the actStart and visStart variables. Selecting pages can be done in one of two contexts: 1) selecting the visible page, i.e. which page is visible on screen, and 2) selecting the active page, i.e. which page is accessed by drawing operations Selecting the active page is just a matter of offsetting our graphics operations by the address of the start of the page, as demonstrated in the put pixel routine below. Selecting the visual page must be passed in to the VGA, by setting the Screen Start register. Sadly enough, the resolution of this register is limited to one addressable byte, which means four pixels in unchained 256-color modes. Some further trickery is needed for 1-pixel smooth, horizontal scrolling, but I'll make that a subject for later. The setXXXStart() functions provided here accept byte offsets as parameters, so they'll work in any mode. If widthBytes and height are set correctly, so will the setXXXPage() functions. ----8<-------cut begin------ /* * setActiveStart() tells our graphics operations which address in video * memory should be considered the top left corner. */ setActiveStart(unsigned offset) { actStart = offset; } /* * setVisibleStart() tells the VGA from which byte to fetch the first * pixel when starting refresh at the top of the screen. This version * won't look very well in time critical situations (games for * instance) as the register outputs are not synchronized with the * screen refresh. This refresh might start when the high byte is * set, but before the low byte is set, which produces a bad flicker. * I won't bother with this now. */ setVisibleStart(unsigned offset) { visStart = offset; outport(CRTC_ADDR, 0x0C); /* set high byte */ outport(CRTC_ADDR+1, visStart >> 8); outport(CRTC_ADDR, 0x0D); /* set low byte */ outport(CRTC_ADDR+1, visStart & 0xff); } /* * setXXXPage() sets the specified page by multiplying the page number * with the size of one page at the current resolution, then handing the * resulting offset value over to the corresponding setXXXStart() * function. The first page number is 0. */ setActivePage(int page) { setActiveStart(page * widthBytes * height); } setVisiblePage(int page) { setVisibleStart(page * widthBytes * height); } ----8<-------cut end------ Due to the use of bit planes, the graphics routines tend to get more complex than in mode 13h, and your first versions will generally tend to be a little slower than mode 13h algorithms. Here's a put pixel routine for any unchained 256-color mode (it assumes that the 'width' variable from the above code is set correctly). Optimizing is left as an exercise to you, the reader. This will be the only drawing operation I'll cover in this article, but all general primitives like lines and circles can be based on this routine. (You'll probably not want to do that though, due to the inefficiency.) ----8<-------cut begin------ putPixel_X(int x, int y, char color) { /* Each address accesses four neighboring pixels, so set Write Plane Enable according to which pixel we want to modify. The plane is determined by the two least significant bits of the x-coordinate: */ outportb(0x3c4, 0x02); outportb(0x3c5, 0x01 << (x & 3)); /* The offset of the pixel into the video segment is offset = (width * y + x) / 4, and write the given color to the plane we selected above. Heed the active page start selection. */ vga[(unsigned)(widthBytes * y) + (x / 4) + actStart] = color; } char getPixel_X(int x, int y) { /* Select the plane from which we must read the pixel color: */ outport(GRAC_ADDR, 0x04); outport(GRAC_ADDR+1, x & 3); return vga[(unsigned)(widthBytes * y) + (x / 4) + actStart]; } ----8<-------cut end------ However, by now you should be aware of that the Write Plane Enable register isn't limited to selecting just one bit plane, like the Read Plane Select register is. You can enable any combination of all four to be written. This ability to access 4 pixels with one instruction helps quadrupling the speed in certain respects, especially when drawing horizontal lines and filling polygons of a constant color. Also, most block algorithms can be optimized in various ways so that they need only a constant number of OUTs (typically four) to the Write Plane Enable register. OUT is a relatively slow instruction. The gained ability to access the full 256 Kb of memory on a standard VGA enables you to do paging and all the goodies following from that: smooth scrolling over large maps, page flipping for flicker free animation... and I'll leave something for your own imagination. In short, the stuff gained from unchaining mode 13h more than upweighs the additional complexity of using a planar mode. Now, the resolution of the mode is of little interest in this context. Nearly any 256-color resolution from (about) 80x8 to 400x300 is available for most VGAs. I'll dwell particularly by 320x240, as this is the mode that Michael Abrash introduced as 'Mode X' in his DDJ articles. It is also the resolution that most people refer to when using that phrase. The good thing about the 320x240 mode is that the aspect ratio is 1:1, which means that each pixel is 'perfectly' square, i.e. not rectangular like in 320x200. An ellipse drawn with the same number of pixels along both main axes will look like a perfect circle in 320x240, but like a subtly tall ellipse in 320x200. Here's a function which sets the 320x240 mode. You'll notice that it depends on the first piece of code above: ----8<-------cut begin------ set320x240x256_X() { /* Set the unchained version of mode 13h: */ set320x200x256_X(); /* Modify the vertical sync polarity bits in the Misc. Output Register to achieve square aspect ratio: */ outportb(0x3C2, 0xE3); /* Modify the vertical timing registers to reflect the increased vertical resolution, and to center the image as good as possible: */ outport(0x3D4, 0x2C11); /* turn off write protect */ outport(0x3D4, 0x0D06); /* vertical total */ outport(0x3D4, 0x3E07); /* overflow register */ outport(0x3D4, 0xEA10); /* vertical retrace start */ outport(0x3D4, 0xAC11); /* vertical retrace end AND wr.prot */ outport(0x3D4, 0xDF12); /* vertical display enable end */ outport(0x3D4, 0xE715); /* start vertical blanking */ outport(0x3D4, 0x0616); /* end vertical blanking */ /* Update mode info, so future operations are aware of the resolution: */ height = 240; } ----8<-------cut end------ As you've figured out, this mode will be completely compatible with the utility functions presented earlier, thanks to the global variable 'height'. Boy, am I foreseeing or what! Other resolutions are achieved through giving other values to the sync timing registers of the VGA, but this is quite a large and complex subject, so I'll postpone this to later, if ever. Anyway, I hope I've helped getting you started using mode X. As far as I know, the two modes I've used above should work on *any* VGA and Super VGA available, so this is pretty stable stuff. Let me know of any trouble, and - good luck! 3. THE ROAD FROM HERE I'm providing information on various libraries and archives which relate to what this article deals with. If you want me to add anything to this list (for future articles), let me know, although I can't promise anything. I am assuming you have ftp access. wuarchive.wustl.edu:/pub/MSDOS_UPLOADS/programming/xlib06.zip This is the current de facto C/assembler library for programming unchained modes (do not confuse with a X Windows library). All sources are included, and the library is totally free. It has functions for pixels, lines, circles, bezier curves, mouse handling, sprites (bitmaps), compiled bitmaps, and supports a number of resolutions. The version number ('06') is current as of November 1993. graphprg.zip Michael Abrash' articles in Doctor Dobbs Journal is always mentioned with awe. In this 350 Kb archive, most of his interesting stuff has been gathered. Read about Mode X development and techniques from month to month. Included is also all the individual source code snippets from each article, and also the full XSHARP library providing linedrawing, polygons, bitmaps, solid 3D projection and speedy rendering, and even an implementation of 2D texture mapping (can be used for quasi-3D texture mapping), plus an article on assembly optimization on the i86 processor family. Definitely recommended. oak.oakland.edu:/pub/msdos/vga/vgadoc2.zip This is a bare bones VGA register reference. It also contains register references for the CGA, EGA and Hercules cards, in addition to dozens of SuperVGAs. Check out the BOOKS section for some decent VGA references though - you don't want to start tweaking without a real one. wuarchive.wustl.edu:/pub/MSDOS_UPLOADS/programming/tweak15b.zip TWEAK might be of interest to the more adventurous reader. TWEAK lets you play around with the registers of the VGA in an interactive manner. Various testing screens for viewing your newmade modes are applied at the press of a key. Version 1.5 adds a test screen which autodetects your graphics mode and displays various information about resolutions etc. Keep a VGA reference handy. Don't try it if this is the first time you've heard of 'registers' or 'mode X' or 'tweaking'. I was planning a version based on the Turbo Vision interface, but time has been short. Maybe later! 4. BOOKS ON THE SUBJECT Extremely little has been published in written form about using 'Mode X'-style modes. Below are some books which cover VGA programming at varying degrees of technical level, but the only one to mention unchained modes and Mode X, is Michael Abrash'. I'd get one of the VGA references first, though. o George Sutty & Steve Blair : "Advanced Pogrammer's Guide to the EGA/VGA" from Brady. A bit old perhaps, but covers all *standard* EGA/VGA registers, and discusses most BIOS functions and other operations. Contains disk with C/Pascal/assembler source code. There's a sequel out for SuperVGAs, which I haven't seen. o Michael Abrash : "Power Graphics Programming" from QUE/Programmer's Journal. Collections of (old) articles from Programmer's Journal on EGA/VGA, read modes and write modes, animation, tweaking (320x400 and 360x480). His newer ravings in DDJ covers fast 256-color bitmaps, compiled bitmaps, polygons, 3D graphics, texture mapping among other stuff. o Richard F. Ferraro : "Programmer's Guide to the EGA and VGA video cards including Super VGA". I don't have this one, but heard it's nice. Detailed coverage of all EGA/VGA registers. The Super VGA reference makes it attractive. o Richard Wilton : "Programmer's Guide to PC & PS/2 Video Systems" Less technical, more application/algorithm oriented. Nice enough, even though it is a bit outdated, in that he discusses CGA and Hercules cards just as much as EGA/VGA. 5. BYE - FOR NOW I am considering writing a text describing in more detail the process of using TWEAK to achieve the VGA resolution you want or need. However, I thought I'd let this document go first, and see if I get any reactions. If I don't, I'll stop. Feel free to forward any suggestions, criticisms, bombs and beers. I can be reached via: o e-mail: robert@stud.unit.no o land mail: Robert Schmidt Stud.post 170 NTH N-7034 Trondheim NORWAY Nothing would encourage or please me more than a postcard from where you live! ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ M1ORG.ASC ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ Figure 1: Memory organization in mode 13h (ASCII version) by Robert Schmidt (C) 1993 Ztiff Zox Softwear a. Imagine that the top of the screen looks like this (pixel values are represented by color digits 0-9 for simplicity - actual colors may range from 0 to 255) - a screen width of 320 pixels is assumed: address: 0 10 310 319 ---------------------------------------- |0123456789012345 ..... 0123456789| | | | | | b. In VGA memory, the screen is represented as follows (question marks represent unused bytes): Plane 0: address: 0 10 310 319 ---------------------------------------- |0???4???8???2??? ..... ??2???6???| | | | | Plane 1: address: 0 10 310 319 ---------------------------------------- |?1???5???9???3?? ..... ???3???7??| | | | | Plane 2: address: 0 10 310 319 ---------------------------------------- |??2???6???0???4? ..... 0???4???8?| | | | | Plane 3: address: 0 10 310 319 ---------------------------------------- |???3???7???1???5 ..... ?1???5???9| | | | | I.e. a plane is selected automatically by the two least significant bits of the address of the byte being read from or written two. This renders 3/4 of the video memory unavailable and useless, but all visible pixels are easily accessed, as each address in the video segment provides access to one and ONLY ONE pixel. ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ MXORG.ASC ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ Figure 2: Memory organization in unchained 256-color modes (like Mode X) (ASCII version) by Robert Schmidt (C) 1993 Ztiff Zox Softwear Imagine that the screen looks the same as in figure 1a. A screen width of 320 pixels is still assumed. In VGA memory, the screen will be represented as follows: Plane 0: address: 0 10 70 79 (NOT 319!) ---------------------------------------- |0482604826048260 ..... 0482604826| | | | | Plane 1: address: 0 10 70 79 ---------------------------------------- |1593715937159371 ..... 1593715937| | | | | Plane 2: address: 0 10 70 79 ---------------------------------------- |2604826048260482 ..... 2604826048| | | | | Plane 3: address: 0 10 70 79 ---------------------------------------- |3715937159371593 ..... 3715937159| | | | | Note that if pixel i is in plane p, pixel i+1 is in plane (p+1)%4. When the planes are unchained, we need to set the Write Plane Enable register to select which planes should receive the data when writing, or the Read Plane Select register when reading. As is evident, one address in the video segment provides access to no less than FOUR different pixels. ÚÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Zox3D ³ ÀÄÄÄÄÄÄÄÙ Available via ftp : ftp.wustl.edu:/pub/MSDOS_UPLOADS/games/programming/zox3d15.zip wasp.eng.ufl.edu:/pub/msdos/demos//zox3d15.zip zox3d15.zip contains a demo of my 3D graphics engine. It resembles Wolf3D, but has a number of additional features: - texture mapped floor and ceiling (sky, in this demo) - real, recursive MIRRORS! - partly TRANSPARENT walls - input from keyboard, joystick and mouse (at the same time, too, if you wish) - controllable camera height - NOT fixed like Wolf3D - quick resizable window - online help and fps rating - advanced collision detection and handling - supports a variety of tweaked X modes, from 256x256 to 400x300. The sky and mirrors have to be seen to be beleived! Zox3D does NOT implement objects, like the guards in Wolf3D, but that should be a breeze to add. The complete sources are available. Read ZOX3D.DOC in the demo archive for information. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Robert Schmidt - robert@stud.unit.no - Buuud@IRC SSSSS CCCCC RRRRR OOOOO LL LL IIIIII NN NN GGGGG SS SS CC CC RR RR OO OO LL LL II NNN NN GG GG SS CC RR RR OO OO LL LL II NNNN NN GG SSSSS CC RR RR OO OO LL LL II NN NN NN GG SS CC RRRRR OO OO LL LL II NN NNNN GG GGG SS SS CC CC RR RR OO OO LL LL II NN NNN GG GG SSSSS CCCCC RR RR OOOOO LLLLL LLLLL IIIIII NN NN GGGGG by Alec Thomas (Kestrel) of FORGE Software Australia (c9223826@cs.newcastle.edu.au) ------------ INTRODUCTION ------------ Okay, here it is fans (and air conditioners, open windows...geez I hate that joke!), how to do scrolling using either X-mode (and associated variants) and standard mode 13h (not hard but I thought I'd put it in anyway :) as well as the basics of parallax scrolling... First things first - X-mode. Throughout this little dissertation, I'm going to assume that you know the basics of X-mode (or mode-X or mode-Y or whatever you want to call it) such as how to get into it, how to set the offset register, etc. and just get on with the scrolling :) I'm not trying to teach you X-mode, but SCROLLING!! One further thing. I'm not saying that the methods I'll explain below are the best method of scrolling, I'm just showing how I got it to work myself in the hope that someone out there can use it. Anyway, enough of this crap, on with the STUFF!!! (just a little note, when I'm talking about rows, they number from 0-199 and the same with columns (except 0-319), etc. unless otherwise stated) ******************************************************************************** * X-MODE SCROLLING * ******************************************************************************** ------------------ VERTICAL SCROLLING ------------------ Ok, this is the easiest form of scrolling using the VGA hardware...fast and clean. The following example assumes you are using 320x200 X-mode with the visible page starting at the top of the first page (offset 0). To scroll what is on the screen up off the top, you simply add 80 (decimal) to the screen offset register. This causes the screen to jump up by one row. However, it also causes whatever is off the bottom of the screen (the next page!) to become visible...not a desireable effect. Easily fixed however. Draw the image you want to scroll, on the row that will scroll on. So, when the screen offset is changed to scroll the screen up, the new data is already there for all to see. Beautiful!!! ----------- Scrolling A (up) -------------- OFFSET = 0 WHILE NOT FINISHED DO OFFSET = OFFSET + 80 DRAW TO ROW 200 SET VGA OFFSET = OFFSET END WHILE ------------------------------------------- Bzzzzz! Wrong! This works fine, until you have scrolled down to the bottom of page 4. Because you're effectively off the bottom of the VGA window (starting at segment A000h), you can't write to the rest of the VGA memory (if there is any - only SVGA's have more than 256K on board memory) and so, you'll be viewing garbage. No problem. The way around it is to only use two pages!!! "What?" I hear you say. In fact, by using only two pages for scrolling, you gain two major advantages: page flipping (because you're only using two pages for the actual scrolling, you can use the spare two to perform page flipping) and infinite scroll regions. You perform the infinite scrolling in exactly the same way as before, with two minor additions: after changing the offset register, you copy the row just scrolled on to the row just scrolled off. Also, after you have scrolled a full page, you reset the offset to the top of the original page. ----------- Scrolling B (up) -------------- OFFSET = 0 WHILE NOT FINISHED DO OFFSET = OFFSET + 80 IF OFFSET >= (200 * 80) THEN OFFSET = 0 DRAW TO ROW 200 SET VGA OFFSET = OFFSET DRAW TO ROW -1 (was row 0 before scroll) END WHILE ------------------------------------------- Ok, so that's how to do vertical scrolling, now on with horizontal scrolling. -------------------- HORIZONTAL SCROLLING -------------------- Horizontal scrolling is essentially the same as vertical scrolling, all you do is increment or decrement the VGA offset register by 1 instead of 80 as with vertical scrolling. However, horizontal scrolling is complicated by two things 1. Incrementing the offset register by one actually scrolls by FOUR pixels (and there are FOUR planes on the VGA, what a coincidence) 2. You can't draw the image off the screen and then scroll it on because of the way the VGA wraps to the next row every 80 bytes (80 bytes * 4 planes = 320 pixels), if you tried it, you would actually be drawing to the other side of the screen (which is entirely visible) I'll solve these problems one at a time. Firstly, to get the VGA to scroll by only one pixel you use the horizontal pixel panning (HPP) register. This register resides at PORT: 3C0H INDEX: 13h and in real life, you use it like this ----------------- Pixel Panning --------------- IN PORT 3DAH (this clears an internal flip-flop of the VGA) OUT 13H TO PORT 3C0H OUT value TO PORT 3C0H (where "value" is the number of pixels to offset) ----------------------------------------------- To implement smooth horizontal scrolling, you would do the following: -------------- Horizontal Scrolling ------------ FOR X = 0 TO 319 DO SET HPP TO ( X MOD 4 ) SET VGA OFFSET TO ( X/4 ) END FOR ------------------------------------------------ Okay, no problem at all (although I think you might have to fiddle around with the HPP a bit to get it right...try different values and see what works :). So, the next problem is with drawing the images off the screen where they aren't visible and then scrolling them on!!! As it turns out, there's yet ANOTHER register to accomplish this. This one's called the offset register (no, not the one I was talking about before, that one was actually the "start address" register) and it's at PORT: 3D4H/3D5H OFFSET: 13H and here's how to use it -------------- Offset Register --------------- OUT 13H TO PORT 3D4H OUT value TO PORT 3D5H ---------------------------------------------- Now, what my VGA reference says is that this register holds the number of bytes (not pixels) difference between the start address of each row. So, in X-mode it normally contains the value 80 (as we remember, 80 bytes * 4 planes = 320 pixels). This register does not affect the VISIBLE width of the display, only the difference between addresses on each row. When we scroll horizontally, we need a little bit of extra working space so we can draw off the edge of the screen. Perhaps a little diagram will clarify it. The following picture is of a standard X-mode addressing scheme with the OFFSET register set to 80. ROW OFFSET 0 0 ======================== 1 80 [ ] 2 160 [ ] .. .. [ VISIBLE ] [ SCREEN ] [ ] [ ] .. .. [ ] 199 15920 ======================== and the next diagram is of a modified addressing scheme with the OFFSET register set to 82 (to give us 4 extra pixels on each side of the screen) ROW OFFSET 0 0 ------========================------ 1 82 | V [ ] V | 2 164 | I [ ] I | .. .. | N S [ VISIBLE ] N S | | O I [ SCREEN ] O I | | T B [ ] T B | | L [ ] L | .. .. | E [ ] E | 199 16318 ------========================------ Beautiful!!! As with vertical scrolling, however, you still have the problem of when you reach the bottom of page 4...and it's fixed in the same manner. I haven't actually managed to get infinite horizontal scrolling working, but the method I have just stated will give you a horizontal scrolling range of over 200 screens!!!! So if you need more (which is extremely unlikely), figure it out yourself. ------------------ COMBINED SCROLLING ------------------ To do both horizontal and vertical scrolling, all you have to do is combine the two methods with a few little extras (it's always the way isn't it). You have to start off with the original screen on the current page and the next page as well. When you scroll horizontally, you have to draw the edge that's coming in to the screen to BOTH pages (that means you'll be drawing the incoming edge twice, once for each page). You do this so that when you have scrolled vertically down through a complete page, you can jump back to the first page and it will (hopefully) have an identical copy, and you can then continue scrolling again. I'm sorry about this being so confusing but it's a bit difficult to explain. ******************************************************************************** * STANDARD VGA SCROLLING * ******************************************************************************** Without X-mode, there is no easy way to do scrolling using the VGA hardware. So basically, you have to resort to redrawing the entire screen for every frame. Several popular games (Raptor and Mortal Kombat spring to mind) utilise this method with excellent effect, so it is quite effective. Basically all you do to implement this is redraw the screen every frame with a slightly different offset into the "map". The following bit of pseudo-code will scroll down and to the right through the map. ------------- Standard Scrolling --------------- X = 0 Y = 0 WHILE NOT FINISHED DO DRAW TO SCREEN( 0, 0 ) FROM MAP( X, Y ) X = X + 1 Y = Y + 1 END WHILE ------------------------------------------------ ******************************************************************************** * PARALLAX SCROLLING * ******************************************************************************** Parallax scrolling is when the "world" appears to have different levels of perspective. That is, images further away from the viewer move proportionately slower than images closer to the screen. To implement parallax scrolling, you need two or more "maps". You start from the most distant map and end with the closest map. When you scroll, you offset the map furthest away by the smallest value and the map closest to you by the largest value. The following pseudo-code implements a 3 level parallax scrolling world, scrolling (as above) down to the right. --------------- Parallax Scrolling ------------------ X = 0 Y = 0 WHILE NOT FINISHED DO DRAW TO SCREEN( 0, 0 ) USING MAP_FAR AT ( X/4, Y/4 ) DRAW TO SCREEN( 0, 0 ) USING MAP_MEDIUM AT ( X/2, Y/2 ) DRAW TO SCREEN( 0, 0 ) USING MAP_NEAR AT ( X, Y ) X = X + 4 Y = Y + 4 END WHILE ----------------------------------------------------- Obviously, with parallax scrolling, each successive map shouldn't delete the previous map entirely. So you'll have to draw the maps using some sort of masking (masking being where you can see through the background colour to what was there previously). ******************************************************************************** * DISCLAIMER * ******************************************************************************** I'm sorry if any of this is confusing, but hey that's half the fun of it - figuring out what the hell I'm raving on about :) So, if you can figure it out, have fun and make games (preferably good ones!) Later, Kestrel => FORGE Software Australia Programming the VGA Registers by Boone (boone@ucsd.edu), March '94 The IBM PC has long been slammed by owners of other computers which come with superior graphics capabilities built right into hardware. The PC is a strange beast to program in general, and when it comes to graphics the programmer doesn't get much help from the video hardware. However, there are quite a few neat tricks you can do using the VGA registers, as I'm sure you're aware. The trick is knowing just which registers to use and how to use them to achieve the desired results. In particular, precise timing is necessary to avoid screen flicker and/or "snow". The registers on your video card are necessary for just about any communication with the VGA besides basic reading/writing of pixels. Some of the registers are standard, which are the ones we will be discussing here. Most SVGA chipsets have their own special functions associated with different registers for things such as bank switching, which is part of what makes trying to write SVGA programs so difficult. The registers are also used to set the various attributes of each video mode: horizontal and vertical resolution, color depth, refresh rate, chain-4 mode, and so on. Luckily, BIOS handles all this for us and since we only need to set the video mode once at program start-up and once at exit, you should need to mess with these particular functions too much, unless you are using a special mode, such as mode X. (See the mode X section for more info on all this.) If you want to experiment with the video mode registers, ftp yourself a file called TWEAK*.* (my version is TWEAK10.ZIP). For now we'll just assume the video mode has already been set to whatever mode you wish. One of the most common techniques used by game programmers is fade in/out. A clean fade is simple but very effective. Suprisingly, even big-budget games like Ultima VII often have a lot of screen noise during their fades. With a little effort you can easily write your own noise-free fade routines. There's nothing like giving a professional first impression on your intro screen, since the fade-in is likely to be the very first thing they see of your program. BIOS is much to slow for this timing-critical opperation, so we'll have to get down and dirty with our VGA card. Fading is a fairly simple process. As you should know, the VGA palette consists of 256 colors with 3 attributes for each color: red, green and blue. Every cycle of the fade, we have to go through all 768 attributes and if it is larger than 0 subtract one. We'll use regsiters 3C8h and 3C9h for palette opperations. The operation for sending a palette to the card is straight-forward: send a 0 to port 3C8h and then your 768 byte buffer to port 3C9h. This is good enough for setting the palette at the start of your program, but of course it has to go in a loop for the fade, since you'll have to do this 256 times, subtracting one from each non-zero member of the buffer. The pseudo-code looks something like this: constant PALSIZE = 256*3; unsigned character buffer[PALSIZE]; boolean done; counter i,j; for j = 255 to 0 { for i = 0 to PALSIZE-1 if buffer[i] > 0 buffer[i] = buffer[i] - 1; output 0 to port 3C8h; for i = 0 to PALSIZE-1 output buffer[i] to port 3C9h; } Easy enough, right? If you convert this to the language of your choice it should run fine. (Make sure you have the buffer pre-loaded with the correct palette, however, or you will get very strange results...) But you'll notice the "snow" mentioned earlier. Depending on your video card, this could mean that you see no noise at all to fuzz covering your entire screen. Even if it look fine on your system, however, we want to make sure it will be smooth on *all* setups it could potentially be run on. For that we're going to have to ask the video card when it's safe to send the palette buffer to the card, and for that we'll need the retrace register. Putting aside palette concerns for a moment, I'll briefly cover the retrace on your video card. (See the next section of this article for a more in-depth discussion of this.) Bascially the vertical retrace is a short time in which the screen is not being updated (from video memory to your monitor) and you can safely do writes to your video memory or palette without worrying about getting snow, flicker, tearing, or other unwanted side-effects. This is a pretty quick period (retrace occurs 60 to 70 times a second) so you can't do too much at once. Returning to our fade: we want to update the palette during the vertical retrace. The value we want is bit 3 of register 3DAh. While that bit is zero we're safe to write. The best practice in this case is to wait for the bit to change to one (screen is being traced) and then the instant it changes to 0, blast all our new video info to the card. It won't be necessary in this case since all we are doing is fading the palette and then waiting for the next retrace, but if you're doing animation or playing music at the same time you'll want to include this extra bit of code as a safety net. Otherwise you might detect the 0 in the refresh bit at the very last instant of the retrace and end up writing while the screen is being traced. The pseudo-code now goes like this: for j = 255 to 0 { for i = 0 to PALSIZE-1 if buffer[i] > 0 buffer[i] = buffer[i] - 1; while bit 3 of port 3DAh is 0 no opperation; while bit 3 of port 3DAh is 1 no opperation; output 0 to port 3C8h; for i = 0 to PALSIZE-1 output buffer[i] to port 3C9h; } That's it! All that's left is for you to implement it in your favorite language. However, I can hear the cries right now: "Code! Give us some real assembly code we can use!" I'm reluctant to provided it as this is the exact sort of thing that is easy to cut and paste into your program without knowing how it works. However, I'll give you the unoptimized main loop in 80x86 assembly as this may be clearer to you that my explanation or pseudo-code. Two things to remember about this code: it is optimized enough to be smooth on any video card (or any that I've seen, anyway) assuming that the fade is the _only_ thing going on. There's some other things you may want to change if you plan to say, play music during this process. Secondly, you'll need to have the current palette loaded into the buffer beforehand. You could read it from the VGA card using either registers or BIOS, but this is both slow and (in my oppinion) sloppy coding. You should *never* ask the video card about anything (excluding retrace) that you could keep track of yourself. In the case of the palette, you probably already loaded it from disk anyway, or if you are using the default palette just read the values once and store them in your executable or in a resource file. palbuf DB 768 DUP (?) fadecnt DW 040h ; At this point, you should: ; 1) have the video mode set ; 2) have palbuf loaded with the current palette ; 3) have something on the screen to fade! fadeloop: xor al,al ; used for comparisons and port 3D8h mov cx,768 ; loop counter mov si,offset palbuf ; save palette buffer in si decloop: mov dl,[si] ; put next pal reg in dx cmp al,dl ; is it 0? je next ; nope... dec dl ; yes, so subtract one mov [si],dl ; put it back into palette buffer next: dec cx ; decrement counter inc si ; increment our buffer cmp cx,0 jne decloop ; not done yet, so loop around mov cx,768 ; reset for palette output sub si,768 ; reset palbuf pointer mov dx,03c8h out dx,al ; inform VGA of palette change inc dx ; DX = 3C8h + 1 = 3C9h mov ch,02h ; do outter loop 2 times mov dx,03dah ; prepare refresh register mov bx,03c9h ; prepare palette reg (for quick loading) cli ; disable interrupts! outloop: mov cl,80h ; do inner loop 128 times in al,dx ; wait for current retrace to end test al,08h jnz $-5 in al,dx ; wait for current screen trace to end test al,08h jz $-5 mov dx,bx ; load up the palette change register innerloop: mov al,[si] ; load next byte of palbuf out dx,al ; send it to the VGA card dec cl ; decrement counter inc si ; increment palbuf pointer cmp cl,0 jne innerloop ; loop while not done dec ch ; decrement outer loop counter cmp ch,0 jne outloop ; loop while not done sti ; restore interrupts mov ax,fadecnt ; entire palette has been sent dec ax ; so check fade loop mov fadecnt,ax cmp ax,0 ; ready to quit? jne fadeloop ; nope, keep fading! I should add a few comments about this code segment. First of all, it assumes you want to fade every color all the way down. You may only want to fade certain sections of the palette (if your screen was only using a certain number of colors) or maybe your palette is low-intensity so you don't need to go the full 256 loops to get every color down to 0. It also goes by ones, so if you want a faster fade you can have it subtract two from each attribute. If you want to fade to a certain color other than black (for instance, fade to red such as the "getting hit" effect in Doom), you'll need to check if each attribute is above or below your target color and increment or decrement accordingly. Also, you may have noticed something in the code absent from the pseudo-code: it only sends 128 colors to the card each retrace! This is because if you use all 256 the next retrace may start before you get all colors sent to the video card, thanks to the unoptimized code. Some recommend as little as 64 colors per retrace, however I've found 128 to be okay and certainly much faster. The above code works for any VGA-equiped machine, regardless of processor, but you'll probably want to compress all the IN and OUT loops into REP INSB/OUTSB, REP INSW/OUTSW, or REP INSD/OUTSD instructions depending upon the minimum processor requirement for your game/demo. I won't describe fading in since it's the same sort of thing, and I'm sure you can figure it out once you know how to use the registers themselves. It's a little more complicated since you need a second buffer of target values for your attributes, but otherwise quite similar. Next up is vertical retrace. This is simply one of many read registers on your VGA, but it happens to be one of the most useful for animation and palette fades (as shown above). Here's a quick rundown of what exactly the vertical retrace is, and why it's useful. There's an electron gun in the back of your monitor that keeps the pixels "refreshed" with their correct values every 1/60th of a second or so. It fires electrons at each pixel, row by row. The horizontal retrace is the time it takes it to return from the right side of the screen after it has traced a row. This is a very short time and I wouldn't worry about that too much right now, as it is only useful for very specialized (and quite tricky) hardware effects. More useful, however, is the vertical retrace which occurs when the electron gun reaches the bottom of the screen (one entire screen traced) and it returns diagonally to the upper-right hand corner of the screen. During this time you are free to update anything you like having to do with video with no noise or interference (since nothing on the screen is being updated). This is a fairly short amount of time, though, so whatever you want to do you better do it _quickly_. For animation, you'll usually want to keep a second buffer in main memory (remember that video RAM is quite slow compared to main RAM) which you can use to write your animations to. When the vertical retrace occurs, you'll want to blast the entire thing to the VGA as quickly as possible, using a memory copy instruction. You can find more on this in articles which cover animation. Lastly I'll briefly describe the VGA mode-set registers. There are quite a number of them and for the most part they're pretty boring. By sending different values to these registers you can achieve the various video modes that your card is capable of. These registers set values such as horizontal and vertical resolution, retrace timing, addressing modes, color depth, timing, and other fun stuff. The truth is that it's easier and just as effective to let the BIOS (gasp!) handle setting the screen mode for you, particularly since most games use standard modes such as 320x200 anyway. At the very least you can let BIOS set the mode to begin with and then just modify the registers to "tweak" the mode the way you want it. Any of these non-BIOS modes are generally refered to as mode X. I don't want to go deep into detail on the setting and usage of mode X because there is already so much info availible on the topic. Check out the Mode X Faq (regularly posted in comp.sys.ibm.pc.demos and rec.games.programmer), Micheal Abrash's collumn in Dr. Dobb's and his X-sharp library, or the section on mode X in the PC-GPE. One mode register I'll cover quickly is the chain-4 enable/disable. A lot of programmers seem to have trouble visualizing what this thing does exactly. Bit 3 of port 3C4h (index 4) controls chain-4 mode. Normally it is on. This allows fast linear addressing of the bytes in video memory, which is the way you are probably used to addressing them. For example, to change the second pixel on the screen to a certain color, you simply write the value to address A000:0001. With chain-4 disabled (the main feature of mode X besides better resolution) A000:0000 refers to the first pixel in the upper-left corner of your screen, A000:0001 refers to the fourth pixel, A000:0002 to the eight pixel and so on. The odd pixels are accessed by changing the write plane. Since there are four planes, you effectively get an extra two bits of addressing space, boosting the total bit width for your pixel addressing from 16 to 18. Standard chain-4 four only allows access to 64K of memory (2^16) while disabling this feature gives you the full 256K (2^18) of memory to work with. The disadvantage, of course, is that pixel writes are slower due to the port writes required to access odd pixels. How can this be an advantage? For one thing, you can write four pixels at a time as long as they are all the same color - handy for single-color polygons, as in flight simulators. Secondly, you get four times as much memory. This allows you to have higher resolutions without bank switching, or scroll the screen using hardware scrolling, or do page flipping for smooth animation. And since you can change the resolution, you can give yourself a sqaure aspect ration (320x240) which is better for bitmap rotations and the like. But remember that it can be slower for bitmapped graphics because you have to do at least four writes to the card (to change planes) in order to copy bitmaps from main RAM to video memory. Don't use mode X just because you think it's "cool"; make sure you have a good reason for wanting to use it in your program, or otherwise you're wasting a lot of effort for no reason. Now, I'm sure you want me to continue until I divulge all the secrets of the VGA register to you - but, I only have some much time and space. Besides, I still haven't uncovered all of their mysteries and capabilities myself. However, below is a list of the registers which you may want to play with for various effects. The following list was posted on rec.games.programmer by Andrew Bromage (bromage@mundil.cs.mu.OZ.AU), so thanks to him for posting in to begin with. That's it for this article and I hope it helped you understand your VGA card a little better. If not, re-read it, and try writing your own programs which use the registers. The only way to really understand it (as with most things) is to get some hands-on experience. If you've got any questions, comments, flames, or corrections related to this document or game programming/design in general, feel free to post an article in rec.games.programmer (in case you haven't noticed by now, I hang out there regularly) or send mail to boone@ucsd.edu. Here's the list. Have fun... Documentation Over the I/O Registers for Standard VGA Cards Documentated by Shaggy of The Yellow One Email: D91-SJD@TEKN.HJ.SE Feel free to spread this to whoever wants it..... ------------------------------------------------------------ Port-Index: - Port: Write/03c2h Read/03cch usage: d7 Vertical sync polarity d6 Horizontal sunc polarity d5 Odd /even page d4 Disable video d3 Clock select 1 d2 Clock select 0 d1 Enable/Disable display RAM d0 I/O address select Description: Sync polarity: Bits are set as below for VGA displays that use sync polarity to determine screen resolution. Many newer multiple frequency displays are insensitive to sync polarity d7 d6 Resolution 0 0 Invalid 0 1 400 lines 1 0 350 lines 1 1 480 lines I/O address select: When set to zero, selects the monochrome I/O address space (3bx). When set to one, it selects the color I/O address space (3dx) ------------------------------------------------------------ Port-Index: - Port: 03c2h ; read only usage: d7 Vertical Retrace Interrupt pendling d6 Feature connector bit 1 d5 Feature connector bit 0 d4 Switch sense d0-d3 Unused Description: d7 uses IRQ2 ------------------------------------------------------------ Port-Index: - Port: 03bah,03dah ; read only usage: d3 Vertical retrace d0 Horizontal retrace ------------------------------------------------------------ Port-Index: - Port: 03c3h,46e8h usage: d7-d1 Reserved d0 VGA enable/disable (03c3h only) Description: Disables access to display memmory and the other VGA's ports ------------------------------------------------------------ Port-Index: 00h Port: 03d4h, 03b4h usage: Horizontal total Description: Total number of characters in horizontal scan minus five ( including blanked and border characters) ------------------------------------------------------------ Port-Index: 01h Port: 03d4h, 03b4h usage: Horizontal display enable Description: Total number of characters displayed in horizontal scan minus one. ------------------------------------------------------------ Port-Index: 02h Port: 03d4h, 03b4h usage: Start horizontal blanking Description: Character at which blanking starts ------------------------------------------------------------ Port-Index: 03h Port: 03d4h, 03b4h usage: End horizontal blanking d7 Test d6 Skew control d5 Skew control d0-d4 End blanking Description: End blanking: is five LSB bits of six-bit value, which define the character at which blanking stops. The MSB bit of this value is in register index 5. ------------------------------------------------------------ Port-Index: 04h Port: 03d4h, 03b4h usage: Start horizontal retrace Description: Character at which horizontal retrace starts ------------------------------------------------------------ Port-Index: 05h Port: 03d4h, 03b4h usage: End horizontal retrace d7 End horizontal blanking bit 5 d6 Horizontal retrace delay d5 Horizontal retrace delay d0-d4 End horizontal retrace Description: End horizontal retrace: defines the character at which horizontal retrace ends ------------------------------------------------------------ Port-Index: 06h Port: 03d4h, 03b4h usage: Vertical total Description: Total number of horizontal scan lines minus two (including blanked and border characters). MSB bits of this value are in register index 7 ------------------------------------------------------------ Port-Index: 07h Port: 03d4h, 03b4h usage: Overflow register d7 Vertical retrace start (bit 9) d6 Vertical display enable end (bit 9) d5 Vertical total (bit 9) d4 Line compare (bit 8) d3 Start vertical blank (bit 8) d2 Vertical retrace start (bit 8) d1 Vertical display enable end (bit 8) d0 Vertical total (bit 8) ------------------------------------------------------------ Port-Index: 08h Port: 03d4h, 03b4h usage: Preset row scan d7 Unused d6 Byte panning control d5 Byte panning control d0-d4 Preset row scan Description: Byte panning control: is used to control byte panning. This register together with attribute controller register 13h allows for up to 31 pixels of panning in double word modes Preset row scan: Which character scan line is the first to be displayed ------------------------------------------------------------ Port-Index: 09h Port: 03d4h, 03b4h usage: Maximum scan line/Character height d7 double scan d6 bit d9 of line compare register d5 bit d9 of start vertical blank register d0-d4 Maximum scan line Description: d0-d5=Character height-1, only in textmodes ------------------------------------------------------------ Port-Index: 0ah Port: 03d4h, 03b4h usage: Cursor start d7,d6 Reserved (0) d5 Cursor off d4-d0 Cursor start Description: ------------------------------------------------------------ Port-Index: 0bh Port: 03d4h, 03b4h usage: Cursor end d7 reserved d6,d5 Cursor skew d4-d0 Cursor end Description: ------------------------------------------------------------ Port-Index: 0ch Port: 03d4h, 03b4h usage: Start address high ------------------------------------------------------------ Port-Index: 0dh Port: 03d4h, 03b4h usage: Start address low Description: Determine the offset in display memory to be displayed on the upper-left corner on the screen ------------------------------------------------------------ Port-Index: 0eh Port: 03d4h, 03b4h usage: Cursor location (high byte) ------------------------------------------------------------ Port-Index: 0fh Port: 03d4h, 03b4h usage: Cursor location (low byte) Description: Where the cursor is displayed on screen ------------------------------------------------------------ Port-Index: 10h Port: 03d4h, 03b4h usage: Vertical retrace start Description: 8 bits out of 10 ------------------------------------------------------------ Port-Index: 11h Port: 03d4h, 03b4h usage: Vertical retrace end d7 Write protect CRTC register 0 to 7 d6 refresh cycle select d5 enable vertical interrupt (when 0) d4 Clear vertical interrupt (when 0) d0-d3 Vertical retrace end ------------------------------------------------------------ Port-Index: 12h Port: 03d4h, 03b4h usage: Vertical display enable end Description: eight LSB bits out of ten-bit value which define scan line minus one at which the display ends. The other two are in CRTC register index 7 ------------------------------------------------------------ Port-Index: 13h Port: 03d4h, 03b4h usage: Offset / Logical screen width Description: Logical screen width between successive scan lines ------------------------------------------------------------ Port-Index: 14h Port: 03d4h, 03b4h usage: Underline location register d7 Reserved d6 Double word mode d5 count by 4 d0-d4 Underline location Description: Underline location: Monochrome textmode only ------------------------------------------------------------ Port-Index: 15h Port: 03d4h, 03b4h usage: Start vertical blanking Description: eight LSB bits of ten-bit value minus one which define at which scan line the vertical blanking starts. The other two bits are in CRTC registers index 7 and 9 ------------------------------------------------------------ Port-Index: 16h Port: 03d4h, 03b4h usage: End vertical blanking Description: eight LSB bits of a value which determine the scan line after which vertical blanking ends. ------------------------------------------------------------ Port-Index: 17h Port: 03d4h, 03b4h usage: Mode control register d7 Enable vertical and hoizontal retrace d6 Byte mode (1), word mode (0) d5 Address wrap d4 Reserved d3 count by 2 d2 multiple vertical by 2 (use half in CRTC (8,10,12,14,18) d1 Select row scan counter (not used) d0 compatibilty mode support (enable interleave) ------------------------------------------------------------ Port-Index: 18h Port: 03d4h, 03b4h usage: Line compare register Description: Split screen, 8 bit value out of a ten-bit value ------------------------------------------------------------ Port-Index: 00h Port: 03c4h usage: Reset register d7-d2 Reserved d1 Synchronous reset d0 Asynchronous reset Description: Synchr. when set to zero, will halt and reset the sequencer at the end of its current cycle Asyncht. when set to zero, will immediatly halt and reset the sequencer. Data can be loss. ------------------------------------------------------------ Port-Index: 01h Port: 03c4h usage: Clock mode register d7,d6 Reserved d5 display off d4 Allow 32-bit Fetch (not used in standard modes) d3 Divide dot clock by 2 (used in some 320*200 modes) d2 Allow 16-bit fetch (used in mon graphics modes) d1 Reserved d0 Enable (0) 9 dot characters (mono text and 400-line) Description: Display off: Will blank screen and give the cpu uninterrupted access the display memory. ------------------------------------------------------------ Port-Index: 02h Port: 03c4h usage: Color plane write enable register d7,d6 Reserved d3 Plane 3 Write enable d2 Plane 2 Write enable d1 Plane 1 Write enable d0 Plane 0 Write enable Description: ------------------------------------------------------------ Port-Index: 03h Port: 03c4h usage: Character generator select register d7,d6 Reserved d5 Character generator table select A (MSB) d4 Character generator table select B (MSB) d3,d2 Character generator table select A d1,d0 Character generator table select B Description: This register is only of interest if your software will be using multiple character sets. Either one or two character sets can be active. Table A selects the charcater with attribute d3 set to zero and Table B is the one with d3 set to one. ------------------------------------------------------------ Port-Index: 04h Port: 03c4h usage: Memory mode register d4-d7 Reserved d3 Chain 4 (address bits 0&1 to select plan, mode 13h) d2 Odd/even (address bit 0 to select plane 0&2 or 1&3 text modes) d1 Extended memory (disable 64k modes) d0 Reserved Description: ------------------------------------------------------------ Port-Index: 00h Port: 03ceh usage: Set / Reset register d7-d4 Reserved (0) d3 Fill data for plane 3 d2 Fill data for plane 2 d1 Fill data for plane 1 d0 Fill data for plane 0 ------------------------------------------------------------ Port-Index: 01h Port: 03ceh usage: Set / Reset enable register d7-d4 Reserved (0) d3 enable set/reset for plane 3 (1 = enable) d2 enable set/reset for plane 2 (1 = enable) d1 enable set/reset for plane 1 (1 = enable) d0 enable set/reset for plane 0 (1 = enable) Description: Set/Reset enable defines which memory planes will receive fill data from set/reset register. Any plane that is disable for set/reset will be written with normal processor output data ------------------------------------------------------------ Port-Index: 02h Port: 03ceh usage: Color compare register d7-d4 Reserved d3 Color compare value for plane 3 d2 Color compare value for plane 2 d1 Color compare value for plane 1 d0 Color compare value for plane 0 Description: one indicate that color is the same ------------------------------------------------------------ Port-Index: 03h Port: 03ceh usage: Data rotate / Function select register d7-d5 Resrved (0) d4,d3 Function select d2-d0 Rotate count d4 d3 Function 0 0 Write data unmodified 0 1 Write data ANDed with processor latches 1 0 Write data ORed with processor latches 1 1 Write data XORed with processor latches Description: Rotation is made before writing data ------------------------------------------------------------ Port-Index: 04h Port: 03ceh usage: Read plane select register d7-d2 Reserved (0) d1,d0 Defines color plane for reading (0-3) Description: Doesnt matter in color compare mode ------------------------------------------------------------ Port-Index: 05h Port: 03ceh usage: Mode register d7 Reserved (0) d6 256-colour mode d5 Shift register mode d4 Odd / Even mode d3 Color compare mode enable (1 = enable) d2 Reserved (0) d1,d0 Write mode d1 d0 Write mode 0 0 Direct write (data rotate, set/reset may apply) 0 1 Use processor latches as write data 1 0 Color plane n (0-3) is filled with the value of bit n in the write data 1 1 Use (rotated) write data ANDed with Bit mask as bit mask. Use set/reset as if set/reset was enable for all planes Description: ------------------------------------------------------------ Port-Index: 06h Port: 03ceh usage: Miscellaneous register d7-d4 Reserved d3-d2 Memory map 00 = A000h for 128k 01 = A000h for 64k 10 = B000h for 32k 11 = B800h for 32k d1 Odd/even enable (used in text modes) d0 Graphics mode enable Description: Memory map defines the location and size of the host window ------------------------------------------------------------ Port-Index: 07h Port: 03ceh usage: Color don't care register d7-d4 Reserved (0) d3 Plane 3 don't care d2 Plane 2 don't care d1 Plane 1 don't care d0 Plane 0 don't care Description: Color don't care is used in conjunction with color compare mode. This register masks particular planes from being tested during color compare cycles. ------------------------------------------------------------ Port-Index: 08h Port: 03ceh usage: Bitmask register Description: The bitmask register is used to mask certain bit positons from being modified. ------------------------------------------------------------ Port-Index: - Port: 03c0h both index and data usage: d7,d6 Reserved d5 Palette address source 0 = palette can be modified, screen is blanked 1 = screen is enable, palette cannot be modified d4-d0 Palette register address Description: Palette register address selects which register of the attributes controller will be addres,sed by the next I/O write cycle ------------------------------------------------------------ Port-Index: 00h-0fh Port: 03c0h usage: Color palette register d6,d7 Reserved d5-d0 Color value Description: not used in 256 color modes ------------------------------------------------------------ Port-Index: 10h Port: 03c0h usage: Mode control register d7 p4,p5 source select d6 pixel width d5 Horizontal panning compatibility d4 Reserved d3 Background intensify / enable blinking d2 Line graphics enable (text modes only) d1 display type d0 graphics / text mode Description: p4,p5 source select: selects the source for video outputs p4 and p5 to the DACs. If set to zero, p4 and p5 are driven from the palette registers (normal operation). If set to one, p4 and p5 video outputs come from bits 0 and 1 of the color select register. pixel width: is set to one in mode 13h (256-color mode) horizontal panning compatibility: enhances the operation of the line compare register of the CRT controller, which allows one section of the screen to be scrolled while another section remains stationary. When this bit is set to one, the stationary section of the screen will also be immune to horizontal panning. ------------------------------------------------------------ Port-Index: 11h Port: 03c0h usage: Screen border color Description: In text modes, the screen border color register selects the color of the border that sorrounds the text display area on the screen. This is also referred to by IBM as overscan. Unfortunately, this feature does not work properly on EGA displays in 350-line modes. ------------------------------------------------------------ Port-Index: 12h Port: 03c0h usage: Color plane enable register d7,d6 Reserved d5,d4 Video status mux d3 Enable color plane 3 d2 Enable color plane 2 d1 Enable color plane 1 d0 Enable color plane 0 Description: The video status mux bits can be used in conjunction with the diagnostic bits of input status register 1 to read palette registers. For the EGA, this is the only means available for reading the palette registers. Enable color planes can be used to enable or disable color planes at the input to the color lockup table. A zero in any of these bit positions will mask the data from that color plane. The effect on the display will be the same as if that color plane were cleared to all zeros. ------------------------------------------------------------ Port-Index: 13h Port: 03c0h usage: Horizontal panning register d7-d4 reserved d3-d0 Horizontal pan Description: Horizontal pan allows the display to be shifted horizontally one pixel at a time. d3-d0 Number of pixels shifted to the left 0+,1+,2+ 13h Other modes 3+,7,7+ 0 1 0 0 1 2 1 - 2 3 2 1 3 4 3 - 4 5 4 2 5 6 5 - 6 7 6 3 7 8 7 - 8 9 - - ------------------------------------------------------------ Port-Index: 14h Port: 03c0h usage: Color select register d7-d4 Reserved d3 color 7 d2 color 6 d1 color 5 d0 color 4 Description: Color 7 and color 6: are normally used as the high order bits of the eight-bit video color data from the attribute controller to the DACs. The only exceptions are 256-color modes Color 5 and color 4: can be used in place of the p5 and p6 outputs from the palette registers (see mode control register - index 10h). In 16-color modes, the color select register can be used to rapidly cycle between sets of colors in the video DAC. ------------------------------------------------------------ Port-Index: - Port: 03c6h usage: Pixel mask register Description: ??? ------------------------------------------------------------ Port-Index: - Port: 03c7h usage: DAC state register (read-only) Description: if d0 and d1 is set to zero it indicates that the lookup table is in a write mode ------------------------------------------------------------ Port-Index: - Port: 03c7h usage: Lookup table read index register (Write only) Description: Used when you want to read the palette (set color number) ------------------------------------------------------------ Port-Index: - Port: 03c8h usage: Lookup table write index register Description: Used when you want to change palette (set color number) ------------------------------------------------------------ Port-Index: - Port: 03c9h usage: Lookup table data register Description: Read color value (Red-Green-Blue) or write same data. ------------------------------------------------------------ ----------1000------------------------------- INT 10 - VIDEO - SET VIDEO MODE AH = 00h AL = mode (see below) Return: AL = video mode flag (Phoenix BIOS) 20h mode > 7 30h modes <= 7 except mode 6 3Fh mode 6 AL = CRT controller mode byte (Phoenix 386 BIOS v1.10) Notes: IBM standard modes do not clear the screen if the high bit of AL is set (EGA or higher only) SeeAlso: AX=0070h,AX=007Eh,AX=10F0h,AX=6F05h,AH=FFh"GO32",INT 5F/AH=00h Values for video mode: text/ text pixel pixel colors display scrn system grph resol box resoltn pages addr 00h = T 40x25 8x14 16gray 8 B800 EGA = T 40x25 8x16 16 8 B800 MCGA = T 40x25 9x16 16 8 B800 VGA 01h = T 40x25 8x14 16 8 B800 EGA = T 40x25 8x16 16 8 B800 MCGA = T 40x25 9x16 16 8 B800 VGA 02h = T 80x25 8x14 16gray 4 B800 EGA = T 80x25 8x16 16 4 B800 MCGA = T 80x25 9x16 16 4 B800 VGA 03h = T 80x25 8x14 16 4 B800 EGA = T 80x25 8x16 16 4 B800 MCGA = T 80x25 9x16 16 4 B800 VGA 04h = G 40x25 8x8 320x200 4 B800 CGA,PCjr,EGA,MCGA,VGA 05h = G 40x25 8x8 320x200 4gray B800 CGA,PCjr,EGA = G 40x25 8x8 320x200 4 B800 MCGA,VGA 06h = G 80x25 8x8 640x200 2 B800 CGA,PCjr,EGA,MCGA,VGA 07h = T 80x25 9x14 mono var B000 MDA,Hercules,EGA = T 80x25 9x16 mono B000 VGA 0Bh = reserved (used internally by EGA BIOS) 0Ch = reserved (used internally by EGA BIOS) 0Dh = G 40x25 8x8 320x200 16 8 A000 EGA,VGA 0Eh = G 80x25 8x8 640x200 16 4 A000 EGA,VGA 0Fh = G 80x25 8x14 640x350 mono 2 A000 EGA,VGA 10h = G 80x25 8x14 640x350 4 2 A000 64k EGA = G 640x350 16 A000 256k EGA,VGA 11h = G 80x30 8x16 640x480 mono A000 VGA,MCGA,ATI EGA,ATI VIP 12h = G 80x30 8x16 640x480 16/256k A000 VGA,ATI VIP = G 80x30 8x16 640x480 16/64 A000 ATI EGA Wonder 13h = G 40x25 8x8 320x200 256/256k A000 VGA,MCGA,ATI VIP ----------1001------------------------------- INT 10 - VIDEO - SET TEXT-MODE CURSOR SHAPE AH = 01h CH = bit 7 should be zero bits 6,5 cursor blink (00=normal, 01=invisible, 10=erratic, 11=slow) (00=normal, other=invisible on EGA/VGA) bits 4-0 top scan line containing cursor CL = bottom scan line containing cursor (bits 0-4) Notes: buggy on EGA systems--BIOS remaps cursor shape in 43 line modes, but returns unmapped cursor shape applications which wish to change the cursor by programming the hardware directly on EGA or above should call INT 10/AX=1130h or read 0040h:0085h first to determine the current font height BUG: AMI 386 BIOS and AST Premier 386 BIOS will lock up the system if AL is not equal to the current video mode SeeAlso: AH=03h,AX=CD05h ----------1002------------------------------- INT 10 - VIDEO - SET CURSOR POSITION AH = 02h BH = page number 0-3 in modes 2&3 0-7 in modes 0&1 0 in graphics modes DH = row (00h is top) DL = column (00h is left) SeeAlso: AH=03h,AH=05h ----------1003------------------------------- INT 10 - VIDEO - GET CURSOR POSITION AND SIZE AH = 03h BH = page number 0-3 in modes 2&3 0-7 in modes 0&1 0 in graphics modes Return: AX = 0000h (Phoenix BIOS) CH = start scan line CL = end scan line DH = row (00h is top) DL = column (00h is left) Notes: a separate cursor is maintained for each of up to 8 display pages many ROM BIOSes incorrectly return the default size for a color display (start 06h, end 07h) when a monochrome display is attached SeeAlso: AH=01h,AH=02h ----------1004------------------------------- INT 10 - VIDEO - READ LIGHT PEN POSITION (EGA Only) AH = 04h Return: AH = light pen trigger flag 00h not down/triggered 01h down/triggered DH,DL = row,column of character light pen is on CH = pixel row (graphics modes 04h-06h) CX = pixel row (graphics modes with >200 rows) BX = pixel column Notes: on a CGA, returned column numbers are always multiples of 2 (320- column modes) or 4 (640-column modes) returned row numbers are only accurate to two lines ----------1005------------------------------- INT 10 - VIDEO - SELECT ACTIVE DISPLAY PAGE AH = 05h AL = new page number (00h to number of pages - 1) (see AH=00h) SeeAlso: AH=0Fh ----------1006------------------------------- INT 10 - VIDEO - SCROLL UP WINDOW AH = 06h AL = number of lines by which to scroll up (00h = clear entire window) BH = attribute used to write blank lines at bottom of window CH,CL = row,column of window's upper left corner DH,DL = row,column of window's lower right corner Note: affects only the currently active page (see AH=05h) Warning: some implementations have a bug which destroys BP SeeAlso: AH=07h,AH=72h,AH=73h ----------1007------------------------------- INT 10 - VIDEO - SCROLL DOWN WINDOW AH = 07h AL = number of lines by which to scroll down (00h=clear entire window) BH = attribute used to write blank lines at top of window CH,CL = row,column of window's upper left corner DH,DL = row,column of window's lower right corner Note: affects only the currently active page (see AH=05h) Warning: some implementations have a bug which destroys BP SeeAlso: AH=06h,AH=72h,AH=73h ----------1008------------------------------- INT 10 - VIDEO - READ CHARACTER AND ATTRIBUTE AT CURSOR POSITION AH = 08h BH = page number (00h to number of pages - 1) (see AH=00h) Return: AH = attribute bit 7: blink bits 6-4: background color 000 black 001 blue 010 green 011 cyan 100 red 101 magenta 110 brown 111 white bits 3-0: foreground color 0000 black 1000 dark gray 0001 blue 1001 light blue 0010 green 1010 light green 0011 cyan 1011 light cyan 0100 red 1100 light red 0101 magenta 1101 light magenta 0110 brown 1110 yellow 0111 light gray 1111 white AL = character Notes: for monochrome displays, a foreground of 1 with background 0 is underlined the blink bit may be reprogrammed to enable intense background colors using AX=1003h or by programming the CRT controller SeeAlso: AH=09h,AX=1003h ----------1009------------------------------- INT 10 - VIDEO - WRITE CHARACTER AND ATTRIBUTE AT CURSOR POSITION AH = 09h AL = character to display BH = page number (00h to number of pages - 1) (see AH=00h) BL = attribute (text mode) or color (graphics mode) if bit 7 set in graphics mode, character is xor'ed onto screen CX = number of times to write character Notes: all characters are displayed, including CR, LF, and BS replication count in CX may produce an unpredictable result in graphics modes if it is greater than the number of positions remaining in the current row SeeAlso: AH=08h,AH=0Ah,AH=4Bh,INT 17/AH=60h,INT 1F,INT 43,INT 44 ----------100A------------------------------- INT 10 - VIDEO - WRITE CHARACTER ONLY AT CURSOR POSITION AH = 0Ah AL = character to display BH = page number (00h to number of pages - 1) (see AH=00h) BL = attribute (PCjr only) or color (graphics mode) if bit 7 set in graphics mode, character is xor'ed onto screen CX = number of times to write character Notes: all characters are displayed, including CR, LF, and BS replication count in CX may produce an unpredictable result in graphics modes if it is greater than the number of positions remaining in the current row SeeAlso: AH=08h,AH=09h,AH=4Bh,INT 17/AH=60h,INT 1F,INT 43,INT 44 ----------100B--BH00------------------------- INT 10 - VIDEO - SET BACKGROUND/BORDER COLOR AH = 0Bh BH = 00h BL = background/border color (border only in text modes) SeeAlso: AH=0Bh/BH=01h ----------100B--BH01------------------------- INT 10 - VIDEO - SET PALETTE AH = 0BH BH = 01h BL = palette ID 00h background, green, red, and brown/yellow 01h background, cyan, magenta, and white SeeAlso: AH=0Bh/BH=00h ----------100C------------------------------- INT 10 - VIDEO - WRITE GRAPHICS PIXEL AH = 0Ch BH = page number AL = pixel color (if bit 7 set, value is xor'ed onto screen) CX = column DX = row Notes: valid only in graphics modes BH is ignored if the current video mode supports only one page SeeAlso: AH=0Dh,AH=46h ----------100D------------------------------- INT 10 - VIDEO - READ GRAPHICS PIXEL AH = 0Dh BH = page number CX = column DX = row Return: AL = pixel color Notes: valid only in graphics modes BH is ignored if the current video mode supports only one page SeeAlso: AH=0Ch,AH=47h ----------100E------------------------------- INT 10 - VIDEO - TELETYPE OUTPUT AH = 0Eh AL = character to write BH = page number BL = foreground color (graphics modes only) Notes: characters 07h (BEL), 08h (BS), 0Ah (LF), and 0Dh (CR) are interpreted and do the expected things IBM PC ROMs dated 4/24/81 and 10/19/81 require that BH be the same as the current active page SeeAlso: AH=02h,AH=0Ah ----------100F------------------------------- INT 10 - VIDEO - GET CURRENT VIDEO MODE AH = 0Fh Return: AH = number of character columns AL = display mode (see AH=00h) BH = active page (see AH=05h) Notes: if mode was set with bit 7 set ("no blanking"), the returned mode will also have bit 7 set EGA, VGA, and UltraVision return either AL=03h (color) or AL=07h (monochrome) in all extended-row text modes SeeAlso: AH=00h,AH=05h,AX=1130h,AX=CD04h ----------101000---------------------------- INT 10 - VIDEO - SET SINGLE PALETTE REGISTER (PCjr,EGA,MCGA,VGA) AX = 1000h BL = palette register number (00h-0Fh) = attribute register number (undocumented) 10h attribute mode control register (should let BIOS control this) 11h overscan color register (see also AX=1001h) 12h color plane enable register (bits 3-0 enable corresponding text attribute bit) 13h horizontal PEL panning register 14h color select register BH = color or attribute register value Notes: on MCGA, only BX = 0712h is supported under UltraVision, the palette locking status (see AX=CD01h) determines the outcome SeeAlso: AX=1002h,AX=1007h,AX=CD01h ----------101001----------------------------- INT 10 - VIDEO - SET BORDER (OVERSCAN) COLOR (PCjr,EGA,VGA) AX = 1001h BH = border color (00h-3Fh) BUG: the original IBM VGA BIOS incorrectly updates the parameter save area and places the border color at offset 11h of the palette table rather than offset 10h Note: under UltraVision, the palette locking status (see AX=CD01h) determines the outcome SeeAlso: AX=1002h,AX=1008h,AX=CD01h ----------101002----------------------------- INT 10 - VIDEO - SET ALL PALETTE REGISTERS (PCjr,EGA,VGA) AX = 1002h ES:DX -> palette register list Note: under UltraVision, the palette locking status (see AX=CD01h) determines the outcome SeeAlso: AX=1000h,AX=1001h,AX=1009h,AX=CD01h Format of palette register list: Offset Size Description 00h 16 BYTEs colors for palette registers 00h through 0Fh 10h BYTE border color ----------101003----------------------------- INT 10 - VIDEO - TOGGLE INTENSITY/BLINKING BIT (Jr, PS, TANDY 1000, EGA, VGA) AX = 1003h BL = new state 00h background intensity enabled 01h blink enabled Note: although there is no function to get the current status, bit 5 of 0040h:0065h indicates the state SeeAlso: AH=08h ----------101007----------------------------- INT 10 - VIDEO - GET INDIVIDUAL PALETTE REGISTER (VGA,UltraVision v2+) AX = 1007h BL = palette or attribute (undoc) register number (see AX=1000h) Return: BH = palette or attribute register value Notes: UltraVision v2+ supports this function even on color EGA systems in video modes 00h-03h, 10h, and 12h; direct programming of the palette registers will cause incorrect results because the EGA registers are write-only. To guard against older versions or unsupported video modes, programs which expect to use this function on EGA systems should set BH to FFh on entry. SeeAlso: AX=1000h,AX=1009h ----------101008----------------------------- INT 10 - VIDEO - READ OVERSCAN (BORDER COLOR) REGISTER (VGA,UltraVision v2+) AX = 1008h Return: BH = border color (00h-3Fh) Notes: UltraVision v2+ supports this function even on color EGA systems in video modes 00h-03h, 10h, and 12h; direct programming of the palette registers will cause incorrect results because the EGA registers are write-only. To guard against older versions or unsupported video modes, programs which expect to use this function on EGA systems should set BH to FFh on entry. SeeAlso: AX=1001h ----------101009----------------------------- INT 10 - VIDEO - READ ALL PALETTE REGISTERS AND OVERSCAN REGISTER (VGA) AX = 1009h ES:DX -> 17-byte buffer (see AX=1002h) Notes: UltraVision v2+ supports this function even on color EGA systems in video modes 00h-03h, 10h, and 12h; direct programming of the palette registers will cause incorrect results because the EGA registers are write-only. To guard against older versions or unsupported video modes, programs which expect to use this function on EGA systems should set the ES:DX buffer to FFh before calling. SeeAlso: AX=1002h,AX=1007h,AX=CD02h ----------101010----------------------------- INT 10 - VIDEO - SET INDIVIDUAL DAC REGISTER (VGA/MCGA) AX = 1010h BX = register number CH = new value for green (0-63) CL = new value for blue (0-63) DH = new value for red (0-63) SeeAlso: AX=1012h,AX=1015h ----------101012----------------------------- INT 10 - VIDEO - SET BLOCK OF DAC REGISTERS (VGA/MCGA) AX = 1012h BX = starting color register CX = number of registers to set ES:DX -> table of 3*CX bytes where each 3 byte group represents one byte each of red, green and blue (0-63) SeeAlso: AX=1010h,AX=1017h ----------101013----------------------------- INT 10 - VIDEO - SELECT VIDEO DAC COLOR PAGE (VGA) AX = 1013h BL = subfunction 00h select paging mode BH = 00h select 4 blocks of 64 BH = 01h select 16 blocks of 16 01h select page BH = page number (00h to 03h) or (00h to 0Fh) Note: not valid in mode 13h SeeAlso: AX=101Ah ----------101015----------------------------- INT 10 - VIDEO - READ INDIVIDUAL DAC REGISTER (VGA/MCGA) AX = 1015h BL = palette register number Return: DH = red value CH = green value CL = blue value SeeAlso: AX=1010h,AX=1017h ----------101017----------------------------- INT 10 - VIDEO - READ BLOCK OF DAC REGISTERS (VGA/MCGA) AX = 1017h BX = starting palette register CX = number of palette registers to read ES:DX -> buffer (3 * CX bytes in size) (see also AX=1012h) Return: buffer filled with CX red, green and blue triples SeeAlso: AX=1012h,AX=1015h ----------101018----------------------------- INT 10 - VIDEO - undocumented - SET PEL MASK (VGA/MCGA) AX = 1018h BL = new PEL value SeeAlso: AX=1019h ----------101019----------------------------- INT 10 - VIDEO - undocumented - READ PEL MASK (VGA/MCGA) AX = 1019h Return: BL = value read SeeAlso: AX=1018h ----------10101A----------------------------- INT 10 - VIDEO - GET VIDEO DAC COLOR-PAGE STATE (VGA) AX = 101Ah Return: BL = paging mode 00h four pages of 64 01h sixteen pages of 16 BH = current page SeeAlso: AX=1013h ----------10101B----------------------------- INT 10 - VIDEO - PERFORM GRAY-SCALE SUMMING (VGA/MCGA) AX = 101Bh BX = starting palette register CX = number of registers to convert SeeAlso: AH=12h/BL=33h ----------1011------------------------------- INT 10 - VIDEO - TEXT-MODE CHARACTER GENERATOR FUNCTIONS (PS, EGA, VGA) AH = 11h The following functions will cause a mode set, completely resetting the video environment, but without clearing the video buffer AL = 00h, 10h: load user-specified patterns ES:BP -> user table CX = count of patterns to store DX = character offset into map 2 block BL = block to load in map 2 BH = number of bytes per character pattern AL = 01h, 11h: load ROM monochrome patterns (8 by 14) BL = block to load AL = 02h, 12h: load ROM 8 by 8 double-dot patterns BL = block to load AL = 03h: set block specifier BL = block specifier (EGA/MCGA) bits 0,1 = block selected by chars with attribute bit 3=0 bits 2,3 = block selected by chars with attribute bit 3=1 (VGA) bits 0,1,4 = block selected by attribute bit 3 = 0 bits 2,3,5 = block selected by attribute bit 3 = 1 AL = 04h, 14h: load ROM 8x16 character set (VGA) BL = block to load The routines called with AL=1xh are designed to be called only immediately after a mode set and are similar to the routines called with AL=0xh, except that: Page 0 must be active. Bytes/character is recalculated. Max character rows is recalculated. CRT buffer length is recalculated. CRTC registers are reprogrammed as follows: R09 = bytes/char-1 ; max scan line (mode 7 only) R0A = bytes/char-2 ; cursor start R0B = 0 ; cursor end R12 = ((rows+1)*(bytes/char))-1 ; vertical display end R14 = bytes/char ; underline loc (*** BUG: should be 1 less ***) SeeAlso: AX=CD10h ----------1011------------------------------- INT 10 - VIDEO - GRAPHICS-MODE CHARACTER GENERATOR FUNCTIONS (PS, EGA, VGA) AH = 11h AL = 20h: set user 8 by 8 graphics characters (INT 1F) ES:BP -> user table AL = 21h: set user graphics characters ES:BP -> user table CX = bytes per character BL = row specifier 00h user set DL = number of rows 01h 14 rows 02h 25 rows 03h 43 rows AL = 22h: ROM 8 by 14 set BL = row specifier (see above) AL = 23h: ROM 8 by 8 double dot BL = row specifier (see above) AL = 24h: load 8x16 graphics characters (VGA/MCGA) BL = row specifier (see above) AL = 29h: load 8x16 graphics characters (Compaq Systempro) BL = row specifier (see above) Notes: these functions are meant to be called only after a mode set UltraVision v2+ sets INT 43 to the appropriate font for AL=22h,23h,24h, and 29h SeeAlso: INT 1F, INT 43 ----------101130----------------------------- INT 10 - VIDEO - GET FONT INFORMATION (EGA, MCGA, VGA) AX = 1130h BH = pointer specifier 00h INT 1Fh pointer 01h INT 43h pointer 02h ROM 8x14 character font pointer 03h ROM 8x8 double dot font pointer 04h ROM 8x8 double dot font (high 128 characters) 05h ROM alpha alternate (9 by 14) pointer (EGA,VGA) 06h ROM 8x16 font (MCGA, VGA) 07h ROM alternate 9x16 font (VGA only) 11h (UltraVision v2+) 8x20 font (VGA) or 8x19 font (autosync EGA) 12h (UltraVision v2+) 8x10 font (VGA) or 8x11 font (autosync EGA) Return: ES:BP = specified pointer CX = bytes/character DL = character rows on screen - 1 Note: for UltraVision v2+, the 9xN alternate fonts follow the corresponding 8xN font at ES:BP+256N SeeAlso: AX=1100h,AX=1120h,INT 1F,INT 43 ----------1012--BL10------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS, EGA, VGA, MCGA) - GET EGA INFO AH = 12h BL = 10h Return: BH = 00h color mode in effect (I/O port 3Dxh) 01h mono mode in effect (I/O port 3Bxh) BL = 00h 64k bytes memory installed 01h 128k bytes memory installed 02h 192k bytes memory installed 03h 256k bytes memory installed CH = feature bits CL = switch settings ----------1012--BL20------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS,EGA,VGA,MCGA) - ALTERNATE PRTSC AH = 12h BL = 20h select alternate print screen routine Notes: installs a PrtSc routine from the video card's BIOS to replace the default PrtSc handler from the ROM BIOS, which usually does not understand screen heights other than 25 lines some adapters disable print-screen instead of enhancing it SeeAlso: INT 05 ----------1012--BL30------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA) - SELECT VERTICAL RESOLUTION AH = 12h BL = 30h AL = vertical resolution 00h 200 scan lines 01h 350 scan lines 02h 400 scan lines Return: AL = 12h if function supported ----------1012--BL31------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA, MCGA) - PALETTE LOADING AH = 12h BL = 31h AL = 00h enable default palette loading 01h disable default palette loading Return: AL = 12h if function supported ----------1012--BL32------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA, MCGA) - VIDEO ADDRESSING AH = 12h BL = 32h AL = 00h enable video addressing 01h disable video addressing Return: AL = 12h if function supported ----------1012--BL33------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA, MCGA) - GRAY-SCALE SUMMING AH = 12h BL = 33h AL = 00h enable gray scale summing 01h disable gray scale summing Return: AL = 12h if function supported SeeAlso: AX=101Bh,AX=BF06h ----------1012--BL34------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (VGA) - CURSOR EMULATION AH = 12h BL = 34h AL = 00h enable alphanumeric cursor emulation 01h disable alphanumeric cursor emulation Return: AL = 12h if function supported ----------1012--BL35------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS) - DISPLAY-SWITCH INTERFACE AH = 12h BL = 35h AL = 00h initial adapter video off 01h initial planar video on 02h switch active video off 03h switch inactive video on 80h *UNDOCUMENTED* set system board video active flag ES:DX -> buffer (128 byte save area if AL = 0, 2 or 3) Return: AL = 12h if function supported ----------1012--BL36------------------------- INT 10 - VIDEO - ALTERNATE FUNCTION SELECT (PS, VGA) - VIDEO REFRESH CONTROL AH = 12h BL = 36h AL = 00h enable refresh 01h disable refresh Return: AL = 12h if function supported ----------1013------------------------------- INT 10 - VIDEO - WRITE STRING (AT and later,EGA) AH = 13h AL = write mode bit 0: update cursor after writing 1: string contains alternating characters and attributes BH = page number BL = attribute if string contains only characters CX = number of characters in string DH,DL = row,column at which to start writing ES:BP -> string to write Notes: recognizes CR, LF, BS, and bell also available PC or XT with EGA or higher HP 95LX only supports write mode 00h BUG: on the IBM VGA Adapter, any scrolling which may occur is performed on the active page rather than the requested page SeeAlso: AH=09h,AH=0Ah ----------101A------------------------------- INT 10 - VIDEO - DISPLAY COMBINATION (PS,VGA/MCGA) AH = 1Ah AL = 00h read display combination code Return: BL = active display code (see below) BH = alternate display code 01h set display combination code BL = active display code (see below) BH = alternate display code Return: AL = 1Ah if function was supported Values for display combination code: 00h no display 01h monochrome adapter w/ monochrome display 02h CGA w/ color display 03h reserved 04h EGA w/ color display 05h EGA w/ monochrome display 06h PGA w/ color display 07h VGA w/ monochrome analog display 08h VGA w/ color analog display 09h reserved 0Ah MCGA w/ digital color display 0Bh MCGA w/ monochrome analog display 0Ch MCGA w/ color analog display FFh unknown display type ----------101B------------------------------- INT 10 - VIDEO - FUNCTIONALITY/STATE INFORMATION (PS,VGA/MCGA) AH = 1Bh BX = implementation type 0000h return functionality/state information ES:DI -> 64 byte buffer for state information (see below) Return: AL = 1Bh if function supported ES:DI buffer filled with state information SeeAlso: AH=15h Format of state information: Offset Size Description 00h DWORD address of static functionality table (see below) 04h BYTE video mode in effect 05h WORD number of columns 07h WORD length of regen buffer in bytes 09h WORD starting address of regen buffer 0Bh WORD cursor position for page 0 0Dh WORD cursor position for page 1 0Fh WORD cursor position for page 2 11h WORD cursor position for page 3 13h WORD cursor position for page 4 15h WORD cursor position for page 5 17h WORD cursor position for page 6 19h WORD cursor position for page 7 1Bh WORD cursor type 1Dh BYTE active display page 1Eh WORD CRTC port address 20h BYTE current setting of register (3?8) 21h BYTE current setting of register (3?9) 22h BYTE number of rows 23h WORD bytes/character 25h BYTE display combination code of active display 26h BYTE DCC of alternate display 27h WORD number of colors supported in current mode 29h BYTE number of pages supported in current mode 2Ah BYTE number of scan lines active (0,1,2,3) = (200,350,400,480) 2Bh BYTE primary character block 2Ch BYTE secondary character block 2Dh BYTE miscellaneous flags bit 0 all modes on all displays on 1 gray summing on 2 monochrome display attached 3 default palette loading disabled 4 cursor emulation enabled 5 0 = intensity; 1 = blinking 6 PS/2 P70 plasma display (without 9-dot wide font) active 7 reserved 2Eh 3 BYTEs reserved (00h) 31h BYTE video memory available 00h = 64K, 01h = 128K, 02h = 192K, 03h = 256K 32h BYTE save pointer state flags bit 0 512 character set active 1 dynamic save area present 2 alpha font override active 3 graphics font override active 4 palette override active 5 DCC override active 6 reserved 7 reserved 33h 13 BYTEs reserved (00h) Format of Static Functionality Table: Offset Size Description 00h BYTE modes supported #1 bit 0 to bit 7 = 1 modes 0,1,2,3,4,5,6 supported 01h BYTE modes supported #2 bit 0 to bit 7 = 1 modes 8,9,0Ah,0Bh,0Ch,0Dh,0Eh,0Fh supported 02h BYTE modes supported #3 bit 0 to bit 3 = 1 modes 10h,11h,12h,13h supported bit 4 to bit 7 reserved 03h 4 BYTEs reserved 07h BYTE scan lines supported bit 0 to bit 2 = 1 if scan lines 200,350,400 supported 08h BYTE total number of character blocks available in text modes 09h BYTE maximum number of active character blocks in text modes 0Ah BYTE miscellaneous function flags #1 bit 0 all modes on all displays function supported 1 gray summing function supported 2 character font loading function supported 3 default palette loading enable/disable supported 4 cursor emulation function supported 5 EGA palette present 6 color palette present 7 color paging function supported 0Bh BYTE miscellaneous function flags #2 bit 0 light pen supported 1 save/restore state function 1Ch supported 2 intensity blinking function supported 3 Display Combination Code supported 4-7 reserved 0Ch WORD reserved 0Eh BYTE save pointer function flags bit 0 512 character set supported 1 dynamic save area supported 2 alpha font override supported 3 graphics font override supported 4 palette override supported 5 DCC extension supported 6 reserved 7 reserved 0Fh BYTE reserved ----------101C------------------------------- INT 10 - VIDEO - SAVE/RESTORE VIDEO STATE (PS50+,VGA) AH = 1Ch AL = 00h return state buffer size Return: BX = number of 64-byte blocks needed 01h save video state ES:BX -> buffer 02h restore video state ES:BX -> buffer containing previously saved state CX = requested states bit 0 video hardware 1 BIOS data areas 2 color registers and DAC state 3-15 reserved Return: AL = 1Ch if function supported ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction to Programming the SVGA Cards ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ SVGA Section Overview ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The vast majority of the information presented in the PC-GPE was obtained from the book "Programmer's Guide to the EGA and VGA Cards - Includes Super VGAs, Second Edition" by Richard Ferraro, ISBN 0-201-57025-4, published by Addison-Wesley. This book is by far the most comprehensive VGA/SVGA reference I have seen to date and is more than worth it's price tag. I heartily recommend it to anyone wishing to do any serious graphics programming for the PC. The PC-GPE SVGA section was originally not going to be included in version 1 due to the fact that I have only been able to verify that the info on the Paradise SVGA is correct. I will include it however, in the hope that everyone (and I mean *EVERYONE*) who reads these files and tries out the routines will e-mail me with the results they get so I can make the modifications in time for version 2. I will need to know these things: 1) Your SVGA board name 2) The id and revision number of the chip inside (if possible) 3) What you tried and the results you got. This applies to *all* routines, bank switching, chip detection etc.... I need to know everything! If a routine doesn't work as expected then let me know if it's doing anything at all. "The routine is stuffed you idiot" won't exactly help me much, but "I can only read pixels in bank 0 you idiot" just might...... And of course there's always the chance that I've misunderstood my references so I need to have my mistakes pointed out to me as well. I'm a big boy...I can take it! ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Writing to the VGA Ports ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Many of the PC-GPE SVGA texts have the PortW Pascal command as follows: PortW[PORTNUM] := VALUE; This command writes a 16 bit word to the port, the same as the asm op code: out dx, ax The effect of this code is the same as the following two Pascal statements: Port[PORTNUM] := Lo(VALUE); Port[PORTNUM + 1] := Hi(VALUE); I'm not sure if this is common to all the PC ports or only works on the VGA. (Perhaps someone could enlighten me?) The PortW command is very handy when writing to the SVGA extended registers. The SVGA register sets are all extensions of the VGA register sets and use an indexed addressing scheme to cut down on the number of ports they use. The texts often have register maps which look similar to the following: PR0A Address Offset A Index : 09h at port 3CEh Read/Write at port 3CFh ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÙ Bank For this particular map, the register name is PR0A Offset A. To select the register and get it ready for reading and/or writing you write the value 09h to port 3CEh (the index port). The register can then be read from or written to port 3CFh (the read/write port). ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Bank Switching ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In real mode, the PC has addresses A000:0000-B000:FFFF allocated for video memory, although most graphics modes only use the A000 segment. If you set an SVGA card to 640x480x256 color mode (for example) then there will be a total of 307200 pixels on the screen. Since each pixel takes up one byte in 256 color modes around 300K of video memory will be used to store the screen data. In most cases all this memory is accessed through the A000 segment. When you initially set the mode, bank 0 on the card will be active and anything you read to or write from this segment will be in the first 64K bytes in video memory (i.e. lines 0-101 and the first 256 bytes in line number 102). If you want to access the next 64K you must switch the card to bank number 1 so that the A000 segment now maps to the second bank, and so forth. The problem here is that each card has a different method of doing the bank switching. The PC-GPE files contain info on how to do the bank switching for a number of the most commonly used SVGA cards. The VESA standard helped inject some sanity into the otherwise chaotic world of SVGA programming by introducing a "standard" method of bank switching for all cards. A note should be made here about bank granularity. In the section above I assumed that bank 0 corresponded to the first 64K, bank 1 to the next etc.. ie each bank has a 64K granularity. This is true for most cards, but some do have smaller granularities (see the table below). The Paradise for instance has a 4K granularity. It's very similar in concept to the PC's segmented memory, segments are 64K long but they have a 16 byte granularity. The Paradise chip's banks are also 64K long, but they have a 4K granularity. All the bank switching code given in the PC-GPE SVGA files adjust for this so that your code can assume the card has a 64K granularity in all cases. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ SVGA Libraries ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ There are a few SVGA libraries available via anonymouse ftp. I haven't had a chance to use any of them yet, but I've heard some of them are pretty good, so they might be worth checking out. Here's two C libraries that I know of: site: ftp.fasttax.com directory: /pc/graphic/scitech/beta filename: svkt44bl.zip site: garbo.uwasa.fi directory: /pc/programming filename: SVGACC20.ZIP ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Common SVGA Cards ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The PC-GPE files contain information on programming the 7 VGA "standards" as covered by Ferraro. According to Ferraro the majority of SVGA cards on the market today conform to one of these standards. The standards are Ati, Chips and Technologies, Genoa, Paradise, Trident, Tseng and Video7. I've also included a file on the VESA specifications (VESASP12.TXT). VESA seems to be the way to go now since public domain drivers are available for most cards and you only need to write one set of graphics drivers if you use it. VESA BIOS calls can be slow however, so if your program needs to do LOTS of bank switching then you may need to work with the cards on a hardware level. The following is a list of common SVGA's along with the chip it is based on, the number of banks the card contains and the modes they support. The GR field is the bank granularity. This information was obtained by examining the configuration files in the shareware program VPIC. VPIC is a great little program which supports numerous graphics file formats as well as all the cards listed below (and a few more). VPIC can be obtained via anonymous ftp from oak.oakland.edu, directory /pub/msdos/gif, filename vpic. I tried to contact the author so I'd feel better about blatently ripping all the info out of his data files but he doesn't seem to have an e-mail address. Are you out there Bob Montgomery? Quite a number of the chip sets in the list are not mentioned in Ferraro. If anyone has information on programming any of them drop me a line. Each mode in the table below has a mode number. To set the mode, load the AX register with this value and do an interrupt 10h. Some modes below have two numbers. In these cases load AX with the first number and BX with the second before calling interrupt 10h. Only 16 and 256 color are presented in the table below. True-color modes are not included in this version. Board Chip Banks Modes Resolution Col GR ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Acumos ACUMOS 8 5Eh 640x400 256 64k 5Fh 640x480 256 64k 5Ch 800x256 256 64k 10h 640x350 16 64k 12h 640x480 16 64k 58h 800x600 16 64k 5dh 1024x768 16 64k Ahead A Chip AHEADA 4/8 60h 640x400 256 64k 61h 640x480 256 64k 62h 800x600 256 64k 6Ah 800x600 16 64k 74h 1024x768 16 64k Ahead B Chip AHEADB 8/16 60h 640x400 256 64k 61h 640x480 256 64k 62h 800x600 256 64k 63h 1024x768 256 64k 6Ah 800x600 16 64k 74h 1024x768 16 64k ATI VGA Wonder ATI OLD 4/8 61h 640x400 256 32k 62h 640x480 256 32k 63h 800x600 256 32k 54h 800x600 16 32k 65h 1024x768 16 64k ATI VGA Wonder+ ATI NEW 4/8/16 61h 640x400 256 32k 62h 640x480 256 32k 63h 800x600 256 32k 64h 1024x768 256 32k 54h 800x600 16 32k 55h 1024x768 16 32k ATI Ultra 8514A GA ATI NEW 4/8/16 61h 640x400 256 32k 62h 640x480 256 32k 63h 800x600 256 32k 54h 800x600 16 32k 55h 1024x768 16 32k ATI XL ATI NEW 4/8/16 61h 640x400 256 32k 62h 640x480 256 32k 63h 800x600 256 32k 64h 1024x768 256 32k 54h 800x600 16 32k 55h 1024x768 16 32k Chips & Technology Chips and 4/8 78h 640x400 256 16k Technologies 79h 640x480 256 16k 7Ah 720x540 256 16k 7Bh 800x600 256 16k 70h 800x600 16 16k 71h 960x720 16 16k 72h 1024x768 16 16k Cirrus Logic GD54 VESA 16/64 4F02h,100h 640x400 256 4k 4F02h,101h 640x480 256 4k 4F02h,103h 800x600 256 4k 4F02h,105h 1024x768 256 4k 4F02h,102h 800x600 16 4k 4F02h,104h 1024x768 16 4k Definicon, 16 Bit TSENG 4000 8/16 2dh 640x350 256 64k 2fh 640x400 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 38h 1024x768 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k 3Dh 1280x1024 16 64k Diamond 24x PARADISE 4/16 5eh 640x400 256 4k 5fh 640x480 256 4k 5ch 800x600 256 4k 60h 1024x768 256 4k 58h 800x600 16 4k 5Dh 1024x768 16 4k 6Ch 1280x960 16 4k 64h 1280x1024 16 4k Diamond Speedstar 24 TSENG 4000 8/16 2dh 640x350 256 64k 2fh 640x400 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 38h 1024x768 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Everex EV-673 EVEREX 4/8 70h,13h 640x350 256 64k 70h,14h 640x400 256 64k 70h,15h 512x480 256 64k 70h,30h 640x480 256 64k 70h,31h 800x600 256 64k 70h,02h 800x600 16 64k 70h,20h 1024x768 16 64k Everev 678 TRIDENT 4/8 70h,14h 640x400 256 64k 8800 70h,15h 512x480 256 64k 70h,30h 640x480 256 64k 70h,31h 800x600 256 64k 70h,02h 800x600 16 64k 70h,20h 1024x768 16 64k Everex Vision VGA HC TSENG 4000 8/16 2fh 640x400 256 64k 2eh 640x480 256 64k Genoa 5400 TSENG 3000 4/8 2dh 640x350 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Genoa 6400 GENOA 4/8 2bh 640x350 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Genoa 7900 24 bit TSENG 4000 8/16 2dh 640x350 256 64k 2fh 640x400 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 38h 1024x768 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Headland 1024i HEADLAND 4/8 6F05h,66h 640x400 256 64k 6F05h,67h 640x480 256 64k 6F05h,68h 720x540 256 64k 6F05h,69h 800x600 256 64k 6F05h,61h 720x540 16 64k 6F05h,62h 800x600 16 64k 6F05h,65h 1024x768 16 64k Hi Res 512 ZYMOS 4/8 5ch 640x400 256 64k 5dh 640x480 256 64k 5eh 800x600 256 64k 6ah 800x600 16 64k 5fh 1024x768 16 64k Maxxon TRIDENT 8800 4/8 5ch 640x400 256 64k 5dh 640x480 256 64k 5eh 800x600 256 64k 5bh 800x600 16 64k 5fh 1024x768 16 64k C&T MK82452 CHIPS AND 8 78h 640x400 256 16k TECHNOLOGIES 79h 640x480 256 16k 70h 800x600 16 16k 72h 1024x768 16 16k NCR 77C22 NCR 8/16 5eh 640x400 256 64k 5fh 640x480 256 64k 5ch 800x600 256 64k 62h 1024x768 256 64k 58h 800x600 16 64k 5dh 1024x768 16 64k OAK OAK 8/16 53h 640x480 256 64k 54h 800x600 256 64k 59h 1024x768 256 64k 52h 800x600 16 64k 56h 1024x768 16 64k Orchid Fahrenheight 1280 S3 8/16 4F02h,201h 640x480 256 64k 4F02h,203h 800x600 256 64k 4F02h,205h 1024x768 256 64k 4F02h,202h 800x600 16 64k 4F02h,204h 1024x768 16 64k 4F02h,206h 1280x960 16 64k 4F02h,206h 1280x1024 16 64k Orchid Pro Designer II TSENG 4000 8/16 2dh 640x350 256 64k 2fh 640x400 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 38h 1024x768 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Paradise VGA Pro PARADISE 4/16 5eh 640x400 256 4k 5fh 640x480 256 4k 5ch 800x600 256 4k 60h 1024x768 256 4k 58h 800x600 16 4k 5Dh 1024x768 16 4k Primus P2000 GA PRIMUS 8/16 2dh 640x480 256 64k 2bh 800x600 256 64k 31h 1024x768 256 64k 37h 1280x1024 256 64k 2ah 800x600 16 64k 30h 1024x768 16 64k 36h 1280x1024 16 64k Compaq QVision QVISION 8/16 32h 640x480 256 4k 38h 1024x768 256 4k 10h 640x350 16 4k 12h 640x480 16 4k 29h 800x600 16 4k 37h 1024x768 16 4k Realtek RTVGA REALTEK 8/16 25h 640x400 256 64k 26h 640x480 256 64k 27h 800x600 256 64k 28h 1024x768 256 64k 1Fh 800x600 16 64k 21h 1024x768 16 64k 2Ah 1280x1024 16 64k Realtek RTVGA REALTEK 8/16 25h 640x400 256 64k 26h 640x480 256 64k 27h 800x600 256 64k 28h 1024x768 256 64k 1Fh 800x600 16 64k 21h 1024x768 16 64k 2Ah 1280x1024 16 64k S3 Graphics Accelerator S3 8/16 4F02h,201h 640x480 256 64k 4F02h,203h 800x600 256 64k 4F02h,205h 1024x768 256 64k 4F02h,202h 800x600 16 64k 4F02h,204h 1024x768 16 64k 4F02h,206h 1280x960 16 64k STB EM 16 TSENG 4000 8/16 2dh 640x350 256 64k 78h 640x400 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 38h 1024x768 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Phoebes TRIDENT 8800CS 4/8 5ch 640x400 256 64k 5dh 640x480 256 64k 5bh 800x600 16 64k 5fh 1024x768 16 64k Maxxon TRIDENT 8800CS 4/8 5ch 640x400 256 64k 5dh 640x480 256 64k 5eh 800x600 256 64k 5bh 800x600 16 64k 5fh 1024x768 16 64k Trident 8900 TRIDENT 8900 8/16 5Ch 640x400 256 64k 5Dh 640x480 256 64k 5Eh 800x600 256 64k 62h 1024x768 256 64k 5Bh 800x600 16 64k 5Fh 1024x768 16 64k Tseng ET-3000 TSENG ET3000 4/8 2dh 640x350 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 29h 800x600 16 64k 36h 960x720 16 64k 37h 1024x768 16 64k Tseng ET-4000 TSENG ET4000 8/16 2dh 640x350 256 64k 2fh 640x400 256 64k 2eh 640x480 256 64k 30h 800x600 256 64k 38h 1024x768 256 64k 29h 800x600 16 64k 37h 1024x768 16 64k Video 7 VRAM VIDEO7 4/8 6f05h,66h 640x400 256 64k 6f05h,67h 640x480 256 64k 6f05h,68h 720x540 256 64k 6f05h,69h 800x600 256 64k 6f05h,61h 720x540 16 64k 6f05h,62h 800x600 16 64k 6f05h,65h 1024x768 16 64k Western Digital 90C PARADISE 4/8 5eh 640x400 256 4k 5fh 640x480 256 4k 5ch 800x600 256 4k 58h 800x600 16 4k 5Dh 1024x768 16 4k VESA Super VGA Standard Video Electronics Standards Association ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2150 North First Street, Suite 360 Phone: (408) 435-0333 San Jose, CA 95131-2020 Fax: (408) 435-8225 Super VGA BIOS Extension Standard #VS911022 October 22, 1991 Document Version 1.0 VBE Version 1.2 PURPOSE ~~~~~~~ To standardize a common software interface to Super VGA video adapters in order to provide simplified software application access to advanced VGA products. SUMMARY ~~~~~~~ The standard provides a set of functions which an application program can use to A) obtain information about the capabilities and characteristics of a specific Super VGA implementation and B) to control the operation of such hardware in terms of video mode initialization and video memory access. The functions are provided as an extension to the VGA BIOS video services, accessed through interrupt 10h. VESA Super VGA Standard VS911022-2 Contents ~~~~~~~~ 1. Introduction ................................................. Page 3 2. Goals and Objectives ......................................... 4 2.1 Video environment information ........................ 4 2.2 Programming support .................................. 4 2.3 Compatibility ........................................ 5 2.4 Scope of standard .................................... 5 3. Standard VGA BIOS ............................................ 6 4. Super VGA Mode Numbers ....................................... 7 5. CPU Video Memory Control ..................................... 9 5.1 Hardware design consideration ........................ 9 5.1.1 Limited to 64k/128k of CPU address space ..... 9 5.1.2 Crossing CPU video memory window boundaries .. 10 5.1.3 Operating on data frolm different areas ...... 10 5.1.4 Combining data from two different windows .... 10 5.2 Different types of hardware windows .................. 11 5.2.1 Single window systems ........................ 11 5.2.2 Dual window systems .......................... 11 6. Extended VGA BIOS ............................................ 12 6.1 Status Information ................................... 12 6.2 00h - Return Super VGA Information ................... 12 6.3 01h - Return Super VGA mode information .............. 14 6.4 02h - Set Super VGA mode ............................. 20 6.5 03h - Return Super VGA mode .......................... 20 6.6 04h - Save/restore Super VGA video state ............. 21 6.7 05h - Super VGGA video memory window control ......... 22 6.8 06h - Set/Get Logical Scan Line Length ............... 23 6.9 07h - Set/Get Display Start .......................... 24 6.10 08h - Set/Get DAC Palette Control .................... 25 7. Application Example .......................................... 26 VESA Super VGA Standard VS911022-3 1. Introduction ~~~~~~~~~~~~~~~~~~~~ This document contains a specification for a standardized interface to extended VGA video modes and functions. The specification consists of mechanisms for supporting standard extended video modes and functions that have been approved by the main VESA committee and non-standard video modes that an individual VGA supplier may choose to add, in a uniform manner that application software can utilize without having to understand the intricate details of the particular VGA hardware. The primary topics of this specification are definitions of extended VGA video modes and the functions necessary for application software to understand the characteristics of the video mode and manipulate the extended memory associated with the video mode. Readers of this document should already be familiar with programming VGAs at the hardware level and Intel iAPX real mode assembly language. Readers who are unfamiliar with programming the VGA should first read one of the many VGA programming tutorials before attempting to understand these extensions to the standard VGA. VESA Super VGA Standard VS911022-4 2. Goals and Objectives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The IBM VGA has become a defacto standard in the PC graphics world. A multitude of different VGA offerings exist in the marketplace, each one providing BIOS or register compatibility with the IBM VGA. More and more of these VGA compatible products implements various supersets of the VGA standard. These extensions range from higher resolutions and more colors to improved performance and even some graphics processing capabilities. Intense competition has dramatically improved the price/performance ratio, to the benefit of the end user. However, several serious problems face a software developer who intends to take advantage of these "Super VGA" environments. Because there is no standard hardware implementation, the developer is faced with widely disparate Super VGA hardware architectures. Lacking a common software interface, designing applications for these environments is costly and technically difficult. Except for applications supported by OEM-specific display drivers, very few software packages can take advantage of the power and capabilities of Super VGA products. The purpose of the VESA VGA BIOS Extension is to remedy this situation. Being a common software interface to Super VGA graphics products, the primary objective is to enable application and system software to adapt to and exploit the wide range of features available in these VGA extensions. Specifically, the VESA BIOS Extension attempts to address the following issues: A) Return information about the video environment to the application, and B) Assist the application in initializing and programming the hardware. 2.1 Video environment information Today, an application has no standard mechanism to determine what Super VGA hardware it is running on. Only by knowing OEM-specific features can an application determine the presence of a particular video board. This often involves reading and testing registers located at I/O addresses unique to each OEM. By not knowing what hardware an application is running on, few, if any, of the extended features of the underlying hardware can be used. The VESA BIOS Extension provides several functions to return information about the video environment. These functions return system level information as well as video mode specific details. Function 00h returns general system level information, including an OEM identification string. The function also returns a pointer to the supported video modes. Function 01h may be used by the application to obtain information about each supported video mode. Function 03h returns the current video mode. VESA Super VGA Standard VS911022-5 2.2 Programming support Due to the fact that different Super VGA products have different hardware implementations, application software has great difficulty in adapting to each environment. However, since each is based on the VGA hardware architecture, differences are most common in video mode initialization and memory mapping. The rest of the architecture is usually kept intact, including I/O mapped registers, video buffer location in the CPU address space, DAC location and function, etc. The VESA BIOS Extension provides several functions to interface to the different Super VGA hardware implementations. The most important of these is Function 02h, Set Super VGA video mode. This function isolates the application from the tedious and complicated task of setting up a video mode. Function 05h provides an interface to the underlying memory mapping hardware. Function 04h enables an application to save and restore a Super VGA state without knowing anything of the specific implementation. 2.3 Compatibility A primary design objective of the VESA BIOS Extension is to preserve maximum compatibility to the standard VGA environment. In no way should the BIOS extensions compromise compatibility or performance. Another but related concern is to minimiza the changes necessary to an existing VGA BIOS. Ram, as well as ROM-based implementations of the BIOS extension should be possible. 2.4 Scope of standard The purpose of the VESA BIOS Extension is to provide support for extended VGA environments. Thus, the underlying hardware architecture is assumed to be a VGA. Graphics software that drives a Super VGA board will perform its graphics output in generally the same way it drives a standard VGA, i.e. writing directly to a VGA style frame buffer, manipulating graphics controller registers, directly programming the palette, etc. No significant graphics processing will be done in hardware. For this reason, the VESA BIOS Extension does not provide any graphics output functions, such as BitBlt, line or circle drawing, etc. An important constraint of the functionalities that can be placed into the VESA BIOS Extension is that ROM space is severely limited in certain existing BIOS implementations. Outside the scope of this VESA BIOS Extension is the handling of different monitors and monitor timings. Such items are dealt with in other VESA fora. The purpose of the VESA BIOS Extension is to provide a standardized software interface to Super VGA graphics modes, independent of monitor and monitor timing issues. VESA Super VGA Standard VS911022-6 3. Standard VGA BIOS ~~~~~~~~~~~~~~~~~~~~~~~~~ A primary design goal with the VESA BIOS Extension is to minimize the effects on the standard VGA BIOS. Standard VGA BIOS functions should need to be modified as little as possible. This is important since ROM, as well as RAM based versions of the extensions, may be implemented. However, two standard VGA BIOS functions are affected by the VESA extension. These are Function 00h (Set video mode) and Function 0Fh (Read current video state). VESA-aware applications will not set the video mode using VGA BIOS function 00h. Nor will such applications use VGA BIOS function 0Fh. VESA BIOS functions 02h (Set Super VGA mode) and 03h (Get Super VGA mode) will be used instead. However, VESA-unaware applications (such as old Pop-Up programs and other TSRs, or the CLS command of MS-DOS), might use VGA BIOS function 0Fh to get the present video mode. Later it may call VGA BIOS function 00h to restore/reinitialize the old video mode. To make such applications work, VESA recommends that whatever value returned by VGA BIOS function 0Fh (it is up to the OEM to define this number) should be used to reinitialize the video mode through VGA BIOS function 00h. Thus, the BIOS should keep track of the last Super VGA mode in effect. It is recommended, but not mandatory, to support output functions (such as TTY-output, scroll, set pixel, etc.) in Super VGA modes. If the BIOS extension doesn't support such output functions, bit D2 (Output functions supported) of the ModeAttributes field (returned by VESA BIOS function 01h) should be clear. VESA Super VGA Standard VS911022-7 4. Super VGA mode numbers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Standard VGA mode numbers are 7 bits wide and presently range from 00h to 13h. OEMs have defined extended video modes in the range 14h to 7Fh. Values in the range 80h to FFh cannot be used, since VGA BIOS function 00h (Set video mode) interprets bit 7 as a flag to clear/not clear video memory. Due to the limitations of 7 bit mode numbers, VESA video mode numbers are 15 bits wide. To initialize a Super VGA mode, its number is passed in the BX register to VESA BIOS function 02h (Set Super VGA mode). The format of VESA mode numbers is as follows: D0-D8 = Mode number If D8 == 0, this is not a VESA defined mode If D8 == 1, this is a VESA defined mode D9-D14 = Reserved by VESA for future expansion (= 0) D15 = Reserved (= 0) Thus, VESA mode numbers begin at 100h. This mode numbering scheme implements standard VGA mode numbers as well as OEM-defined mode numbers as subsets of the VESA mode number. That means that regular VGA modes may be initialized through VESA BIOS function 02h (Set Super VGA mode), simply by placing the mode number in BL and clearing the upper byte (BH). OEM-defined modes may be initialized in the same way. To date, VESA has defined a 7-bit video mode number, 6Ah, for the 800x600, 16-color, 4-plane graphics mode. The corresponding 15-bit mode number for this mode is 102h. The following VESA mode numbers have been defined: GRAPHICS TEXT 15-bit 7-bit Resolution Colors 15-bit 7-bit Columns Rows mode mode mode mode number number number number ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100h - 640x400 256 108h - 80 60 101h - 640x480 256 109h - 132 25 102h 6Ah 800x600 16 10Ah - 132 43 103h - 800x600 256 10Bh - 132 50 10Ch - 132 60 104h - 1024x768 16 105h - 1024x768 256 106h - 1280x1024 16 107h - 1280x1024 256 VESA Super VGA Standard VS911022-8 10Dh - 320x200 32K (1:5:5:5) 10Eh - 320x200 64K (5:6:5) 10Fh - 320x200 16.8M (8:8:8) 110h - 640x480 32K (1:5:5:5) 111h - 640x480 64K (5:6:5) 112h - 640x480 16.8M (8:8:8) 113h - 800x600 32K (1:5:5:5) 114h - 800x600 64K (5:6:5) 115h - 800x600 16.8M (8:8:8) 116h - 1024x768 32K (1:5:5:5) 117h - 1024x768 64K (5:6:5) 118h - 1024x768 16.8M (8:8:8) 119h - 1280x1024 32K (1:5:5:5) 11Ah - 1280x1024 64K (5:6:5) 11Bh - 1280x1024 16.8M (8:8:8) VESA Super VGA Standard VS911022-9 5. CPU Video Memory Windows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A standard VGA sub-system provides 256k bytes of memory and a corresponding mechanism to address this memory. Super VGAs and their modes require more than the standard 256k bytes of memory but also require that the address space for this memory be restricted to the standard address space for compatibility reasons. CPU video memory windows provide a means of accessing this extended VGA memory within the standard CPU address space. This chapter describes how several hardware implementations of CPU video memory windows operate, their impact on application software design, and relates them to the software model presented by the VESA VGA BIOS extensions. The VESA CPU video memory windows functions have been designed to put the performance insensitive, non-standard hardware functions into the BIOS while putting the performance sensitive, standard hardware functions into the application. This provides portability among VGA systems together with the performance that comes from accessing the hardware directly. In particular, the VESA BIOS is responsible for mapping video memory into the CPU address space while the application is responsible for performing the actual memory read and write operations. This combination software and hardware interface is accomplished by informing the application of the parameters that control the hardware mechanism of mapping the video memory into the CPU address space and then letting the application control the mapping within those parameters. 5.1 Hardware 5.1.1 Limited to 64k/128k of CPU address space The first consideration in implementing extended video memory is to give access to the memory to application software. The standard VGA CPU address space for 16 color graphics modes is typically at segment A000h for 64k. This gives access to the 256k bytes of a standard VGA, i.e. 64k per plane. Access to the extended video memory is accomplished by mapping portions of the video memory into the standard VGA CPU address space. Every Super VGA hardware implementation provides a mechanism for software to specify the offset from the start of video memory which is to be mapped to the start of the CPU address space. Providing both read and write access to the mapped memory provides a necessary level of hardware support for an application to manipulate the extended video memory. VESA Super VGA Standard VS911022-10 5.1.2 Crossing CPU video memory window boundaries The organization of most software algorithms which perform video operations consists of a pair of nested loops: and outer loop over rows or scan lines and an inner loop across the row or scan line. The latter is the proverbial inner loop, which is the bottle neck to high performance software. If a target rectangle is large enough, or poorly located, part of the required memory may be with within the video memory mapped into the CPU address space and part of it may not be addressable by the CPU without changing the mapping. It is desirable that the test for remapping the video memory is located outside of the inner loop. This is typically accomplished by selecting the mapping offset of the start of video memory to the start of the CPU address space so that at least one entire row or scan line can be processed without changing the video memory mapping. There are currently no Super VGAs that allow this offset to be specified on a byte boundary and there is a wide range among Super VGAs in the ability to position a desired video memory location at the start of the CPU address space. The number of bytes between the closest two bytes in video memory that can be placed on any single CPU address is defined as the granularity of the window function. Some Super VGA systems allow any 4k video memory boundary to be mapped to the start of the CPY address space, while other Super VGA systems allow any 64k video memory boundary to be mapped to the start of the CPU address space. These two example systems would have granularities of 4k and 64k, respectively. This concept is very similar to the bytes that can be accessed with a 16 bit pointer in an Intel CPU before a segment register must be changed (the granularity of the segment register or mapping here is 16 bytes). Notes ~~~~~ If the granularity is equal to the length of the CPU address space, i.e. the least significant address bit of the hardware mapping function is more significant than the most significant bit of the CPU address, then the inner loop will have to contain the test for crossing the end or beginning of the CPU address space. This is because if the length of the CPU address space (which is the granularity in this case) is not evenly divisible by the length of a scan line, then the scan line at the end of the CPU address will be in two different video memory which cannot be mapped into the CPU address space simultaneously. 5.1.3 Operating on data from different areas It is sometimes required or convenient to move or combine data from two different areas of video memory. One example of this is storing menus in the video memory beyond the displayed memory because there is hardware support in all VGAs for transferring 32 bits of video data with an 8 bit CPU read and write. Two separately mappable CPU video memory windows must be used if the distance between the source and destination is larger than the size of the CPU video memory window. 5.1.4 Combining data from two different windows The above example of moving data from one CPU video memory window to another CPU video memory only required read access to one window and only required write access to the other window. Sometimes it is convenient to have read access to both windows and write access to one window. An example of this would be a raster operation where the resulting destination is the source data logically combined with the original destination data. VESA Super VGA Standard VS911022-11 5.2 Different types of hardware windows Different hardware implementations of CPU video memory windows can be supported by the VESA BIOS extension. The information necessary for an application to understand the type of hardware implementation is provided by the BIOS to the application. There are three basic types of hardware windowing implementations and they are described below. The types of windowing schemes described below do not include differences in granularity. Also note that is possible for a VGA to use a CPU address space of 128k starting at segment A000h. 5.2.1 Single window systems Some hardware implementations only provide a single window. This single window will be readable as well as writeable. However, this causes a significant performance degradation when moving data in video memory a distance that is larger than the CPU address space. 5.2.2 Dual window systems Many Super VGAs provide two windows to facilitate moving data within video memory. There are two separate methods of providing two windows. 5.2.2.1 Overlapping windows Some hardware implementations distinguish window A and window B by determining if the CPU is attempting to do a memory read or a memory write operation. When the two windows are distinguished by whether the CPU is trying to read or write they can, and usually do, share the same CPU address space. However, one window will be read only and the other will be write only. 5.2.2.2 Non-overlapping windows Another mechanism used by two window systems to distinguish window A and window B is by looking at the CPU address within the total VGA CPU address space. When the two windows are distinguished by the CPU address within the VGA CPU address space the windows cannot share the same address space, but they can each be both read and written. VESA Super VGA Standard VS911022-12 6. Extended VGA BIOS ~~~~~~~~~~~~~~~~~~~~~~~~~ Several new BIOS calls have been defined to support Super VGA modes. For maximum compatibility with the standard VGA BIOS, these calls are grouped under one function number. This number is passed in the AH register to the INT 10h handler. The designated Super VGA extended function number is 4Fh. This function number is presently unused in most, if not all, VGA BIOS implementations. A standard VGA BIOS performs no action when function call 4Fh is made. Super VGA Standard VS900602 defines subfunctions 00h through 07h. Subfunction numbers 08h through 0FFh are reserved for future use. 6.1 Status Information Every function returns status information in the AX register. The format of the status word is as follows: AL == 4Fh: Function is supported Al != 4Fh: Function is not supported AH == 00h: Function call successful AH == 01h: Function call failed Software should treat a non-zero value in the AH register as a general failure condition. In later versions of the VESA BIOS Extension new error codes might be defined. 6.2 Function 00h - Return Super VGA Information The purpose of this function is to provide information to the calling program about the general capabilities of the Super VGA environment. The function fills an information block structure at the address specified by the caller. The information block size is 256 bytes. Input: AH = 4Fh Super VGA support AL = 00h Return Super VGA information ES:DI = Pointer to buffer Output: AX = Status (All other registers are preserved) VESA Super VGA Standard VS911022-13 The information block has the following structure: VgaInfoBlock STRUC VESASignature db 'VESA' ; 4 signature bytes VESAVersion dw ? ; VESA version number OEMStringPtr dd ? ; Pointer to OEM string Capabilities db 4 dup(?) ; capabilities of the video environment VideoModePtr dd ? ; pointer to supported Super VGA modes TotalMemory dw ? ; Number of 64kb memory blocks on board Reserved db 236 dup(?) ; Remainder of VgaInfoBlock VgaInfoBlock ENDS The VESASignature field contains the characters 'VESA' if this is a valid block. The VESAVersion is a binary field which specifies what level of the VESA standard the Super VGA BIOS conforms to. The higher byte specifies the major version number. The lower byte specifies the minor version number. The current VESA version number is 1.2. Applications written to use the features of a specific version of the VESA BIOS Extension, are guaranteed to work in later versions. The VESA BIOS Extension will be fully upwards compatible. The OEMStringPtr is a far pointer to a null terminated OEM-defined string. The string may used to identify the video chip, video board, memory configuration, etc. to hardware specific display drivers. There are no restrictions on the format of the string. The Capabilities field describes what general features are supported in the video environment. The bits are defined as follows: D0 = DAC is switchable 0 = DAC is fixed width, with 6-bits per primary color 1 = DAC width is switchable D1-31 = Reserved The VideoModePtr points to a list of supported Super VGA (VESA-defined as well as OEM-specific) mode numbers. Each mode number occupies one word (16 bits). The list of mode numbers is terminated by a -1 (0FFFFh). Please refer to chapter 2 for a description of VESA mode numbers. The pointer could point into either ROM or RAM, depending on the specific implementation. Either the list would be a static string stored in ROM, or the list would be generated at run-time in the information block (see above) in RAM. It is the application's responsibility to verify the current availability of any mode returned by this Function through the Return Super VGA mode information (Function 1) call. Some of the returned modes may not be available due to the video board's current memory and monitor configuration. The TotalMemory field indicates the amount of memory installed on the VGA board. Its value represents the number of 64kb blocks of memory currently installed. VESA Super VGA Standard VS911022-14 6.3 Function 01h - Return Super VGA mode information This function returns information about a specific Super VGA video mode that was returned by Function 0. The function fills a mode information block structure at the address specified by the caller. The mode information block size is maximum 256 bytes. Some information provided by this function is implicitly defined by the VESA mode number. However, some Super VGA implementations might support other video modes than those defined by VESA. To provide access to these modes, this function also returns various other information about the mode. Input: AH = 4Fh Super VGA support AL = 01h Return Super VGA mode information CX = Super VGA video mode (mode number must be one of those returned by Function 0) ES:DI = Pointer to 256 byte buffer Output: AX = Status (All other registers are preserved) The mode information block has the following structure: ModeInfoBlock STRUC ; mandatory information ModeAttributes dw ? ; mode attributes WinAAttributes db ? ; window A attributes WinBAttributes db ? ; window B attributes WinGranularity dw ? ; window granularity WinSize dw ? ; window size WinASegment dw ? ; window A start segment WinBSegment dw ? ; window B start segment WinFuncPtr dd ? ; pointer to windor function BytesPerScanLine dw ? ; bytes per scan line ; formerly optional information (now mandatory) XResolution dw ? ; horizontal resolution YResolution dw ? ; vertical resolution XCharSize db ? ; character cell width YCharSize db ? ; character cell height NumberOfPlanes db ? ; number of memory planes BitsPerPixel db ? ; bits per pixel NumberOfBanks db ? ; number of banks MemoryModel db ? ; memory model type BankSize db ? ; bank size in kb NumberOfImagePages db ? ; number of images Reserved db 1 ; reserved for page function VESA Super VGA Standard VS911022-15 ; new Direct Color fields RedMaskSize db ? ; size of direct color red mask in bits RedFieldPosition db ? ; bit position of LSB of red mask GreenMaskSize db ? ; size of direct color green mask in bits GreenFieldPosition db ? ; bit position of LSB of green mask BlueMaskSize db ? ; size of direct color blue mask in bits BlueFieldPosition db ? ; bit position of LSB of blue mask RsvdMaskSize db ? ; size of direct color reserved mask in bits DirectColorModeInfo db ? ; Direct Color mode attributes Reserved db 216 dup(?) ; remainder of ModeInfoBlock ModeInfoBlock ENDS The ModeAttributes field describes certain important characteristics of the video mode. Bit D0 specifies whether this mode can be initialized in the present video configuration. This bit can be used to block access to a video mode if it requires a certain monitor type, and that this monitor is presently not connected. Prior to Version 1.2 of the VESA BIOS Extension, it was not required that the BIOS return valid information for the fields after BytesPerScanline. Bit D1 was used to signify if the optional information was present. Version 1.2 of the VBE requires that all fields of the ModeInfoBlock contain valid data, except for the Direct Color fields, which are valid only if MemoryModel field is set to a 6 (Direct Color) or 7 (YUV). Bit D1 is now reserved, and must be set to a 1. Bit D2 indicates whether the BIOS has support for output functions like TTY output, scroll, pixel output, etc. in this mode (it is recommended, but not mandatory, that the BIOS have support for all output functions). If bit D2 is 1 then the BIOS must support all of the standard output functions. The field is defined as follows: D0 = Mode supported in hardware 0 = Mode not supported in hardware 1 = Mode supported in hardware D1 = 1 (Reserved) D2 = Output functions supported by BIOS 0 = Output functions not supported by BIOS 1 = Output functions supported by BIOS D3 = Monochrome/color mode (see note below) 0 = Monochrome mode 1 = Color mode D4 = Mode type 0 = Text mode 1 = Graphics mode D5-D15 = Reserved VESA Super VGA Standard VS911022-16 Note: Monochrome modes have their CRTC address at 3B4h. Color modes have their CRTC address at 3D4h. Monochrome modes have attributes in which only bit 3 (video) and bit 4 (intensity) of the attribute controller output are significant. Therefore, monochrome text modes have attributes of off, video, high intensity, blink, etc. Monochrome graphics modes are two plane graphics modes and have attributes of off, video, high intensity, and blink. Extended two color modes that have their CRTC address at 3D4h are color modes with one bit per pixel and one plane. The standard VGA modes 06h and 11h would be classified as color modes, while the standard VGA modes 07h and 0Fh would be classified as monochrome modes. The BytesPerScanline field specifies how many bytes each logical scanline consists of. The logical scanline could be equal to or larger then the displayed scanline. VESA Super VGA Standard VS911022-17 The WinAAttributes and WinBAttributes describe the characteristics of the CPU windowing scheme such as whether the windows exist and are read/writeable, as follows: D0 = Window supported 0 = Window is not supported 1 = Window is supported D1 = Window readable 0 = Window is not readable 1 = Window is readable D2 = Window writeable 0 = Window is not writeable 1 = Window is writeable D3-D7 = Reserved If windowing is not supported (bit D0 = 0 for both Window A and Window B), then an application can assume that the display memory buffer resides at the standard CPU address appropriate for the MemoryModel of the mode. WinGranularity specifies the smallest boundary, in KB, on which the window can be placed in the video memory. The value of this field is undefined if Bit D0 of the appropriate WinAttributes field is not set. WinSize specifies the size of the window in KB. WinASegment and WinBSegment address specify the segment addresses where the windows are located in CPU address space. WinFuncAddr specifies the address of the CPU video memory windowing function. The windowing function can be invoked either through VESA BIOS function 05h, or by calling the function directly. A direct call will provide faster access to the hardware paging registers than using Int 10h, and is intended to be used by high performance applications. If this field is Null, then Function 05h must be used to set the memory window, if paging is supported. The XResolution and YResolution specify the width and height of the video mode. In graphics modes, this resolution is in units of pixels. In text modes, this resolution is in units of characters. Note that text mode resolutions, in units of pixels, can be obtained by multiplying XResolution and YResolution by the cell width and height, if the extended information is present. The XCharCellSize and YCharSellSize specify the size of the character cell in pixels. The NumberOfPlanes field specifies the number of memory planes available to software in that mode. For standard 16-color VGA graphics, this would be set to 4. For standard packed pixel modes, the field would be set to 1. The BitsPerPixel field specifies the total number of bits that define the color of one pixel. For example, a standard VGA 4 Plane 16-color graphics mode would have a 4 in this field and a packed pixel 256-color graphics mode would specify 8 in this field. The number of bits per pixel per plane can normally be derived by dividing the BitsPerPixel field by the NumberOfPlanes field. VESA Super VGA Standard VS911022-18 The MemoryModel field specifies the general type of memory organization used in this mode. The following models have been defined: 00h = Text mode 01h = CGA graphics 02h = Hercules graphics 03h = 4-plane planar 04h = Packed pixel 05h = Non-chain 4, 256 color 06h = Direct Color 07h = YUV 08h-0Fh = Reserved, to be defined by VESA 10h-FFh = To be defined by OEM In Version 1.1 and earlier of the VESA Super VGA BIOS Extension, OEM defined Direct Color video modes with pixel formats 1:5:5:5, 8:8:8, and 8:8:8:8 were described as a Packed Pixel model with 16, 24, and 32 bits per pixel, respectively. In Version 1.2 and later of the VESA Super VGA BIOS Extension, it is recommended that Direct Color modes use the Direct Color MemoryModel and use the MaskSize and FieldPosition fields of the ModeInfoBlock to describe the pixel format. BitsPerPixel is always defined to be the total memory size of the pixel, in bits. The NumberOfBanks field specifies the number of banks in which the scan lines are grouped. The remainder from dividing the scan line number by the number of banks is the bank that contains the scan line and the quotient is the scan line number within the bank. For example, CGA graphics modes have two banks and Hercules graphics mode has four banks. For modes that don't have scanline banks (such as VGA modes 0Dh-13h), this field should be set to 1. The BankSize field specifies the size of a bank (group of scan lines) in units of 1KB. For CGA and Hercules graphics modes this is 8, as each bank is 8192 bytes in length. For modes that don't have scanline banks (such as VGA modes 0Dh-13h), this field should be set to 0. The NumberOfImagePages field specifies the number of additional complete display images that will fit into the VGA's memory, at one time, in this mode. The application may load more than one image into the VGA's memory if this field is non-zero, and flip the display between them. The Reserved field has been defined to support a future VESA BIOS extension feature and will always be set to one in this version. The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the size, in bits, of the red, green, and blue components of a direct color pixel. A bit mask can be constructed from the MaskSize fields using simple shift arithmetic. For example, the MaskSize values for a Direct Color 5:6:5 mode would be 5, 6, 5, and 0, for the red, green, blue, and reserved fields, respectively. Note that in the YUV MemoryModel, the red field is used for V, the green field is used for Y, and the blue field is used for U. The MaskSize fields should be set to 0 in modes using a MemoryModel that does not have pixels with component fields. VESA Super VGA Standard VS911022-19 The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and RsvdFieldPosition fields define the bit position within the direct color pixel or YUV pixel of the least significant bit of the respective color component. A color value can be aligned with its pixel field by shifting the value left by the FieldPosition. For example, the FieldPosition values for a Direct Color 5:6:5 mode would be 11, 5, 0, and 0, for the red, green, blue, and reserved fields, respectively. Note that in the YUV MemoryModel, the red field is used for V, the green field is used for Y, and the blue field is used for U. The FieldPosition fields should be set to 0 in modes using a MemoryModel that does not have pixels with component fields. The DirectColorModeInfo field describes important characteristics of direct color modes. Bit D0 specifies whether the color ramp of the DAC is fixed or programmable. If the color ramp is fixed, then it can not be changed. If the color ramp is programmable, it is assumed that the red, green, and blue lookup tables can be loaded using a standard VGA DAC color registers BIOS call (AX=1012h). Bit D1 specifies whether the bits in the Rsvd field of the direct color pixel can be used by the application or are reserved, and thus unusable. D0 = Color ramp is fixed/programmable 0 = Color ramp is fixed 1 = Color ramp is programmable D1 = Bits in Rsvd field are usable/reserved 0 = Bits in Rsvd field are reserved 1 = Bits in Rsvd field are usable by the application Notes ~~~~~ Version 1.1 and later VESA BIOS extensions will zero out all unused fields in the Mode Information Block, always returning exactly 256 bytes. This facilitates upward compatibility with future versions of the standard, as any newly added fields will be designed such that values of zero will indicate nominal defaults or non-implementation of optional features (for example, a field containing a bit-mask of extended capabilities would reflect the absence of all such capabilities). Applications that wish to be backwards compatible to Version 1.0 VESA BIOS extensions should pre-initialize the 256 byte buffer before calling Return Super VGA mode information. VESA Super VGA Standard VS911022-20 6.4 Function 02h - Set Super VGA video mode This function initializes a video mode. The BX register contains the mode to set. The format of VESA mode numbers is described in chapter 2. If the mode cannot be set, the BIOS should leave the video environment unchanged and return a failure error code. Input: AH = 4Fh Super VGA support AL = 02h Set Super VGA video mode BX = Video mode D0-D14 = Video mode D15 = Clear memory flag 0 = Clear video memory 1 = Don't clear video memory Output: AX = Status (All other registers are preserved) 6.5 Function 03h - Return current video mode This function returns the current video mode in BX. The format of VESA video mode numbers is described in chapter 2 of this document. Input: AH = 4Fh Super VGA support AL = 03h Return current video mode Output: AX = Status BX = Current video mode (All other registers are preserved) Notes ~~~~~ In a standard VGA BIOS, function 0Fh (Read current video state) returns the current video mode in the AL register. In D7 of AL, it also returns the status of the memory clear bit (D7 of 40:87). This bit is set if the mode was set without clearing memory. In this Super VGA function, the memory clear bit will not be returned in BX since the purpose of the function is to return the video mode only. If an application wants to obtain the memory clear bit, it should call VGA BIOS function 0Fh. VESA Super VGA Standard VS911022-21 6.6 Function 04h - Save/Restore Super VGA video state These functions provide a mechanism to save and restore the Super VGA video state. The functions are a superset of the three subfunctions under standard VGA BIOS function 1Ch (Save/restore video state). The complete Super VGA video state (except video memory) should be saveable/restoreable by setting the requested states mask (in the CX register) to 000Fh. Input: AH = 4Fh Super VGA support AL = 04h Save/restore Super VGA video state DL = 00h Return save/restore state buffer size CX = Requested states D0 = Save/restore video hardware state D1 = Save/restore video BIOS data state D2 = Save/restore video DAC state D3 = Save/restore Super VGA state Output: AX = Status BX = Number of 64-byte blocks to hold the state buffer (All other registers are preserved) Input: AH = 4Fh Super VGA support AL = 04h Save/restore Super VGA video state DL = 01h Save Super VGA video state CX = Requested states (see above) ES:BX = Pointer to buffer Output: AX = Status (All other registers are preserved) Input: AH = 4Fh Super VGA support AL = 04h Save/restore Super VGA video state DL = 02h Restore Super VGA video state CX = Requested states (see above) ES:BX = Pointer to buffer Output: AX = Status (All other registers are preserved) Notes ~~~~~ Due to the goal of complete compatibility with the VGA environment, the standard VGA BIOS function 1Ch (Save/restore VGA state) has not been extended to save the Super VGA video state. VGA BIOS compatibility requires that function 1Ch returns a specific buffer size with specific contents, in which there is no room for the Super VGA state. VESA Super VGA Standard VS911022-22 6.7 Function 05h - CPU Video Memory Window Control This function sets or gets the position of the specified window in the video memory. The function allows direct access to the hardware paging registers. To use this function properly, the software should use VESA BIOS Function 01h (Return Super VGA mode information) to determine the size, location, and granularity of the windows. Input: AH = 4Fh Super VGA support AL = 05h Super VGA video memory window control BH = 00h Select Super VGA video memory window BL = Window number 0 = Window A 1 = Window B DX = Window position in video memory (in window granularity units) Output: AX = Status (See notes below) Input: AH = 4Fh Super VGA support AL = 05h Super VGA video memory window control BH = 01h Return Super VGA video memory window BL = Window number 0 = Window A 1 = Window B Output: AX = Status DX = Window position in video memory (in window granularity units) (See notes below) Notes ~~~~~ This function is also directly accessible through a far call from the application. The address of the BIOS function may be obtained by using VESA BIOS Function 01h, Return Super VGA mode information. A field in the ModeInfoBlock contains the address of this function. Note that this function may be different among video modes in a particular BIOS implementation, so the function pointer should be obtained after each set mode. In the far call version, no status information is returned to the application. Also, the AX and DX registers will be destroyed. Therefore, if AX and/or DX must be preserved, the application must do so priot to making the far call. The application must load the input arguments in BH, BL, and DX (for set window) but does not need to load either AH or AL in order to use the far call version of this function. VESA Super VGA Standard VS911022-23 6.8 Function 06h - Set/Get Logical Scan Line Length This function sets or gets the length of a logical scan line. This function allows an application to set up a logical video memory buffer that is wider than the displayed area. Function 07h then allows the application to set the starting position that is to be displayed. Input: AH = 4Fh Super VGA support AL = 06h Logical Scan Line Length BL = 00h Select Scan Line Length CX = Desired width in pixels Output: AX = Status BX = Bytes Per Scan Line CX = Actual Pixels Per Scan Line DX = Maximum Number of Scan Lines Input: AH = 4Fh Super VGA support AL = 06h Logical Scan Line Length BL = 01h Return Scan Line Length Output: AX = Status BX = Bytes Per Scan Line CX = Actual Pixels Per Scan Line DX = Maximum Number of Scan Lines Notes ~~~~~ The desired width in pixels may not be achieveable because of VGA hardware considerations. The next larger value will be selected thta will accommodate the desired number of pixels, and the actual number of pixels will be returned in CX. BX returns a value that, when added to a pointer into video memory, will point to the next scan line. For example, in a mode 13h this would be 320, but in mode 12h this would be 80. DX returns the number of logical scan lines based upon the new scan line length and the total memory installed and useable in this display mode. This function is also valid in text modes. In text modes, the application should find out the current character cell width through normal BIOS functions, multiply that times the desired number of characters per line, and pass the value in the CX register. VESA Super VGA Standard VS911022-24 6.9 Function 07h - Set/Get Display Start This function selects the pixel to be displayed in the upper left corner of the display from the logical page. This function can be used to pan and scroll around logical screens that are larger than the displayed screen. This function can also be used to rapidly switch between two different displayed screens for double buffered animation effects. Input: AH = 4Fh Super VGA support AL = 07h Display Start Control BH = 00h Reserved and must be 0 BL = 00h Select Display Start CX = First Displayed Pixel in Scan Line DX = First Displayed Scan Line Output: AX = Status Input: AH = 4Fh Super VGA support AL = 07h Display Start Control BL = 01h Return Display Start Output: AX = Status BH = 00h Reserved and will be 0 CX = First Displayed Pixel in Scan Line DX = First Displayed Scan Line Notes ~~~~~ This function is also valid in text modes. In text modes, the application should find out the current character cell width through normal BIOS functions, multiply that times the desired starting character column, and pass that value in the CX register. It should also multiply the current character cell height times the desired starting character row, and pass that value in the DX register. VESA Super VGA Standard VS911022-25 6.10 Function 08h - Set/Get DAC Palette Control This function queries and selects the operating mode of the DAC palette. Some DACs are configurable to provide 6-bits, 8-bits, or more of color definition per red, green, and blue primary color. The DAC palette width is assumed to be reset to standard VGA 6-bits per primary during a standard or VESA Set Super VGA Mode (AX = 4F02h) call. Input: AH = 4Fh Super VGA support AL = 08h Set/Get DAC Palette Control BL = 00h Set DAC palette width BH = Desired number of bits of color per primary (Standard VGA = 6) Output: AX = Status BH = Current number of bits of color per primary (Standard VGA = 6) Input: AH = 4Fh Super VGA support AL = 08h Set/Get DAC Palette Control BL = 01h Get DAC palette width Output: AX = Status BH = Current number of bits of color per primary (Standard VGA = 6) Notes ~~~~~ An application can find out if DAC switching is available by querying Bit D0 of the Capabilities field of the VgaInfoBlock structure returned by VESA Return Super VGA Information (AX = 4F00h). The application can then attempt to set the DAC palette width to the desired value. If the Super VGA is not capable of selecting the requested palette width, then the next lower value that the Super VGA is capable of will be selected. The resulting palette width is returned. VESA Super VGA Standard VS911022-26 7. Application Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following sequence illustrates how an application interface to the VESA BIOS Extension. The hypothetical application is VESA-aware and calls the VESA BIOS functions. However, the application is not limited to supporting just VESA-defined video modes. This it will inquire what video modes are available before setting up the video mode. 1) The application would first allocate a 256 byte buffer. This buffer will be used by the VESA BIOS to return information about the video environment. Some applications will statically allocate this buffer, others will use system calls to temporarily obtain buffer space. 2) The application would then call VESA BIOS function 00h (Return Super VGA information). If the AX register does not contain 004Fh on return from the function call, the application can determine that the VESA BIOS Extension is not present and handle such situation. If no error code is passed in AX, the function call was successful. The buffer has been filled by the VESA BIOS Extension with various information. The application can verify that indeed this is a valid VESA block by identifying the characters 'VESA' in the beginning of the block. The application can inspect the VESAVersion field to determine whether the VESA BIOS Extension ha sufficient functionality. The application may use the OEMStringPtr to locate OEM-specific information. Finally, the application can obtain a list of the supported Super VGA modes by using the VideoModePtr. This field points to a list of the video modes supported by the video environment. 3) The application would then create a new buffer and call the VESA BIOS function 01h (Return Super VGA mode information) to obtain information about the supported video modes. Using the VideoModePtr obtained in step 2) above, the application would call this function with a new mode number until a suitable video mode is found. If no appropriate video mode is found, it is up to the application to handle this situation. The Return Super VGA mode information function fills a buffer specified by the application with information describing the features of the video mode. The data block contains all the information an application needs to take advantage of the video mode. The application would examine the ModeAttributes field. To verify that the mode indeed is supported, the application would inspect bit D0. If D0 is clear, then the mode is not supported by the hardware. This might happen is a specific mode requires a certain type of monitor but that monitor is not present. 4) After the application has selected a video mode, the next step is to initialize the mode. However, the application might first want to save the present video mode. When the application exits, this mode would be restored. To obtain the present video mode, the VESA BIOS function 03h (Get Super VGA mode) would be used. If a non-VESA (standard VGA or OEM-specific) mode is in effect, only the lower byte in the mode number is filled. The upper byte is cleared. 5) To initialize the video mode, the application would use VESA BIOS function 02h (Set Super VGA mode). The application has from this point on full access to the VGA hardware and video memory. VESA Super VGA Standard VS911022-27 6) When the application is about to terminate, it would restore the prior video mode. The prior video mode, obtained in step 4) above could be either a standard VGA mode, OEM-specific mode, or VESA-supported mode. It would reinitialize the video mode by calling VESA BIOS function 02h (Set Super VGA mode). The application would then exit. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the ATI Technologies SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Locating the Extended Register Set ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The ATI extended register set is based on the vga's index register scheme, ie you write the value of the register you want to modify to Index Register Port and write the actual data to the Data Port (the Data Port is one port number higher than the Index Register Port). The value of the Index Register for the ATI extended register set is stored in a word in BIOS ROM at C000:0010. Apparently ATI want to change the value of this register in future so they recommend you obtain it by reading the value at this memory address. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the ATI Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The ATI chip can be identified by checking the string in memory locations C000:0031-003A for the following characters : 761295520 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which ATI Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The first version of the ATI chip is the 18800. The second version is the 28800, which from a programming perspective is identical to the 18800-2. The 18800 can be identified by it's lack of support for display mode 55h. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Determining the ATI Chip Revision Number ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The ATI chip revision number is stored at BIOS location C000:0043. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ATI Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors Chip ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 53h 800x600 16 18800 ³ ³ 54h 800x600 16 18800 ³ ³ 55h 1024x768 16 (planar) 18800-1 ³ ³ 61h 640x400 256 18800 ³ ³ 62h 640x480 256 18800 ³ ³ 63h 800x600 256 18800 ³ ³ 65h 1024x768 256 (packed) 18800 ³ ³ 67h 1024x768 4 ? ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ATI Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In the following examples the EXT variable is the extended register index value obtained from reading the word at C000:0010. The ATI supports both single and duel bank memory mapping. It supports 64K byte pages, each of these can be mapped into the host address space. Single or duel bank mode is selected by the E2B bit in register BE Index : BEh at port EXT Read/Write at port EXT + 1 ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄ E2B 0 = Single Bank Mode 1 = Duel Bank Mode Selecting a bank to write to in single bank mode is done by writing the bank number to the Bank Select Register : Index : B2h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÄÙ Bank number The following procedure will select a bank in single bank mode : Port[EXT] := $B2; Port[EXT + 1] := (Port[EXT + 1] And $E1) Or (bank_number shl 1); where bank_number = 0 - 15. Each bank is 64K long and has a 64K granularity. Duel Bank Mode is only supported on the 18800-1 and 28800 chips. You can map one bank to A000:0000-FFFF for read operations and another to the same address space for write operations. Index : B2h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÂÄÄÄÙ ÀÄÄÄÂÄÄÄÙ Read Write Bank Bank Number Number The following code will set the write bank number: Port[EXT] := $B2; Port[EXT + 1] := (Port[EXT + 1] And $F0) Or (write_bank_number shl 1); The following code will set the read bank number: Port[EXT] := $B2; Port[EXT + 1] := (Port[EXT + 1] And $0F) Or (read_bank_number shl 5); ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ATI IsModeAvailable BIOS Call ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Int 10h Inputs : AH = 12h Extended VGA Control BX = 5506h Get Mode Information BP = FFFF Set up for Return Argument AL = Mode Number Mode number you want to test Returns: BP = FFFFh Mode not supported Anything else : mode is supported, BP = offset into CRTC table for mode ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the Chips And Technologies SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Setup mode ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ To modify some of the CAT's internal SVGA registers the card must be placed into setup mode. This is done by writing the value 1Eh to port 46E8h. To exit setup mode write the value 0Eh to port 46E8h. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Enabling Extensions ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The CAT's extended registers are normally locked and must be enabled before you attempt to modify them. To enable them, you must enter setup mode, write the value 80h to port 103h and exit setup mode. To disable them you must enter setup mode, write the value 00h to port 103h and exit setup mode. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the CAT Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Detecting the presence of a CAT chip can be done by entering setup mode, checking that the value returned from reading port 104h is A5h and then exiting setup mode. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which CAT Chip and Revision Number ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The CAT chip type and revision number can be determined by enabling extensions, reading the value of register 0 and disabling extensions. The top 4 bits (4-7) are the chip id and the lower 4 are the version number. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Chip ID Chip ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 1 82c451 or 82c452 ³ ³ 2 82c455 ³ ³ 3 82c453 ³ ³ 5 82c456 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The 82c451 and 82c452 can be distinguished by attempting to modify register 3Ah (Graphics Cursor Color 1, make sure you set it back to what it was). If the register exists the chip is an 82c452. Alternatively the chip ID can be determined using the Get Controller Information BIOS call (see below). ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ CAT Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors Chip ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 25h 640x480 16 451/452/453 ³ ³ 6Ah 800x600 16 451/452/453 ³ ³ 70h 800x600 16 451/452/453 ³ ³ 71h 960x720 16 452 ³ ³ 72h 1024x768 16 452/453 ³ ³ 78h 640x400 256 451/452/453 ³ ³ 79h 640x480 256 452/453 ³ ³ 7Ah 768x576 256 452 ³ ³ 7Ch 800x600 256 453 ³ ³ 7Eh 1024x768 256 453 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ The CAT Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The following registers can only be modified while the extended registers are enabled (See Enabling Extensions above). The 451, 455 and 456 are always in single-paging mode and have 4 64K banks. To switch to a bank you must first enable access to extended memory with the following procedure: Port[$3D6] := $0B; Port[$3D6] := Port[$3D6] and $FD; Selecting a bank can be done with the following procedure: Port[$3D6] := $0B; Port[$3D7] := bank_number; where bank_number = 0 - 3. Each bank is 64K long and has a 86K granularity. The 452 and 453 banks have a 16K granularity, so if you want 64K granularity you must multiply the bank number by 4 before writing it to the registers : Port[$3D6] := $10; Port[$3D7] := bank_number Shl 2; { = bank_numer * 4 } The 452 and 453 allow duel paging. The 64K host address space is split in two, one low area A000:0000-7FFFh and a high area A000:8000-FFFFh. This mode can be enabled with the following procedure: Port[$3D6] := $10; Port[$3D6] := Port[$3D6] or 2; In this mode each bank also has a granularity of 16K. The lower bank is selected with the same procedure for setting the bank in single-paging mode. The upper bank is selected with the following call: Port[$3D6] := $11; Port[$3D7] := bank_number Shl 2; { = bank_numer * 4 } None of the CAT chips allow you to select one bank for reading and another for writing. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ CAT Get Controller Information BIOS Call ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Int 10h Inputs : AH = 5Fh Extended VGA Control AL = 00h Get Controller Information Returns: AL = 5Fh Extended VGA control function supported BL = Chip type bits 7-4 contain the chip type number 0 = 82c451 1 = 82c452 2 = 82c455 ? = 82c453 bits 3-0 contain the revision number BH = Memory Size Video memory size 0 = 256k 1 = 512k 2 = 1M ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the Genoa SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Genoa has produced 2 SVGA cards. The earlier Genoa cards were based on the Tseng ET3000 chip, the more recents cards are based on the Genoa chip. This file will deal only with the cards based on the Genoa chip (the GVGA). ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ The Extended Register Set ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Genoa uses the same ports as the VGA sequencer register set to access most of it's extended registers, ie the Index Register port for the Genoa is 3C4h and Data port is 3C5h. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the Genoa SVGA Card ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ To identify if a Genoa SVGA is present read the byte at address C000:0000. Let's call this byte SIG_OFFSET. Next read the four bytes at C000:SIG_OFFSET. These four bytes should have the following values : ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Memory Address Value ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ C000:SIG_OFFSET 77h ³ ³ C000:SIG_OFFSET + 1 xx ³ ³ C000:SIG_OFFSET + 2 66h ³ ³ C000:SIG_OFFSET + 3 99h ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which Genoa Card is Present ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The value of the byte at C000:SIG_OFFSET + 1 is the chip identify code. The values for each of the Genoa cards is as follows ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ xx Card Chip ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 33h 5100/5200 Tseng ET3000 ³ ³ 55h 5300/5400 Tseng ET3000 ³ ³ 22h 6100 Genoa GVGA ³ ³ 00h 6200/6300 Genoa GVGA ³ ³ 11h 6400/6600 Genoa GVGA ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ There is no method for determining the card revision number. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Genoa Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ All Genoa cards support the following graphics modes : ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 59h 720x512 16 ³ ³ 5Bh 640x350 256 ³ ³ 5Ch 640x480 256 ³ ³ 5Dh 720x512 256 ³ ³ 5Eh 800x600 256 ³ ³ 5Fh 1024x768 16 ³ ³ 6Ah 800x600 16 ³ ³ 6Ch 800x600 256 ³ ³ 73h 640x480 16 ³ ³ 79h 800x600 16 ³ ³ 7Ch 512x512 16 ³ ³ 7Dh 512x512 256 ³ ³ 7Eh 640x400 256 ³ ³ 7Fh 1024x768 4 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Genoa Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Two banks can be mapped to the segment A000:0000-FFFFh, one for reading and one for writing. The banks can be selected by writing to the Memory Segment Register : Index : 06h at port 3C4h Read/Write at port 3C5h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ MEM ÄÄÙ ÀÄÄÄÂÄÄÄÙ ÀÄÄÄÂÄÄÄÙ Write Read Bank Bank The following code can be used to set the write bank: Port[$3C4] := $06; Port[$3C5] := (Port[$3C5] and $C7) or (write_bank_number shl 3); The following code can be used to set the read bank: Port[$3C4] := $06; Port[$3C5] := (Port[$3C5] and $F8) or read_bank_number; There are 8 banks (numbered 0 -7). Each bank is 64K long, has a 64K granularity and is mapped to host memory A000:0000-FFFFh. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the Paradise SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Western Digital have made a series of Paradise chips, the PVGA1A, WD90C00 and WD90C11. Each chip is fully compatible with it's predecessors. There is also a WD90C10 which is a stripped down version of the WD90C00 used for motherboard VGA implementations and does not support 256 color modes higher that 320x200; this chip will not be discussed here. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Paradise Extensions ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ To modify any of the Paradise extended registers you must enable the extensions. Disable them once you are done. To enable extensions: PortW[$3CE] := $050F; { Extensions on } PortW[$3D4] := $8529; { Unlock PR10-PR17 } PortW[$3C4] := $4806; { Unlock extended sequencer } To disable extensions : PortW[$3CE] := $000F; { Extensions off } PortW[$3D4] := $0029; { Lock PR10-PR17 } PortW[$3C4] := $0006; { Lock extended sequencer } ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the Paradise SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ To identify if a Paradise SVGA chip is present read the 4 bytes at memory address C000:007D. These bytes should be the string "VGA=". ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Memory Address Value ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ C000:007Dh 86d ('V') ³ ³ C000:007Eh 71d ('G') ³ ³ C000:007Fh 65d ('A') ³ ³ C000:0080h 61d ('=') ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which Paradise Chip is Present ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Paradise chip present can be determined by trying to access selected registers. The following pseudo-code will determine the chip id: var old_value : byte; Enable Extensions { Test for a PVGA1A } Port[$3D4] := $2B old_value := Port[$3D5] Port[$3D5] := $AA if Port[$3D5] <> $AA then begin chip is a PVGA1A Port[$3D5] := old_value return end Port[$3D5] := old_value { Distinguish between WD90C00 and WD90C10 } Port[$3C4] := $12 old_value := Port[$3C5] Port[$3C5] := old_value and $BF if (Port[$3C5] and $40) <> 0 then begin chip is a WD90C00 return end Port[$3C5] := old_value or $40 if (Port[$3C5] and $40) = 0 then begin chip is a WD90C00 Port[$3C5] := old_value return end Port[$3C5] := old_value { Distinguish between WD90C10 and WD90C11 } Port[$3C4] := $10 old_value := Port[$3C5] Port[$3C5] := old_value and $FB if (Port[$3C5] and $04) <> 0 then begin chip is a WD90C10 Port[$3C5] := old_value return end Port[$3C5] := old_value or $04 if (Port[$3C5] and $04) = 0 then begin chip is a WD90C10 Port[$3C5] := old_value return end { We made it this far so it's a WD90C11 } chip is a WD90C11 Port[$3C5] := old_value ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Paradise Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors Chips ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 58h 800x600 16 pVGA1, WDC90cxx ³ ³ 59h 800x600 2 pVGA1, WDC90cxx ³ ³ 5Eh 640x400 256 pVGA1, WDC90cxx ³ ³ 5Fh 640x480 256 pVGA1, WD90cxx ³ ³ 5Ah 1024x768 2 WD90cxx ³ ³ 5Bh 1024x768 4 WD90cxx ³ ³ 5Dh 1024x768 16 WD90cxx, c11 (512K) ³ ³ 5Ch 800x600 256 WD90c11 (512K) ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Paradise Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Remember, extensions must be enabled before any of the following procedures are called. The Paradise can work in either single-paging mode, duel-paging mode or read/write mode. There are two registers used to select banks in each of the Paradise bank selection modes: PR0A Address Offset A Index : 09h at port 3CEh Read/Write at port 3CFh ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÙ Bank PR0B Address Offset A Index : 0Ah at port 3CEh Read/Write at port 3CFh ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÙ Bank There are 128 banks and the bank granularity is 4k, so if you want a bank granularity of 64k you must multiply the bank number by 16. Single Paging Mode ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In single paging mode PR0A is set to map a bank to host memory at A000:0000-FFFFh. The bank is used for both reading and writing operations. To set up for single paging mode use the following procedure: Port[$3C4] := $11; { Disable read/write mode } Port[$3C5] := Port[$3C5] and $7F; Port[$3CE] := $0B; { Disable PR0B } Port[$3CF] := Port[$3CF] and $F7; To set a 64k bank number in single paging mode use the following procedure: PortW[$3CE] := bank_number Shl 12 + $09; Duel Paging Mode ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In duel paging mode PR0A is set to map a bank to host memory at A000:0000-7FFFh and PR0B is set to map a bank to host memory at A000:8000-FFFFh. Each bank is used for both reading and writing operations. To set up for duel paging mode use the following procedure: Port[$3C4] := $11; { Disable read/write mode } Port[$3C5] := Port[$3C5] and $7F; Port[$3CE] := $0B; { Enable PR0B } Port[$3CF] := Port[$3CF] or $80; To set the lower bank use the same procedure as given for single-paging mode. The upper bank can be set with the following procedure: PortW[$3CE] := bank_number Shl 12 + $0A; Read/Write Paging Mode ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In read/write paging mode PR0A is used to map a bank at A000:0000-FFFFh for read operations and PR0B is used to map a bank at A000:0000-FFFFh for write operations. To set up for read/write paging mode use the following procedure: Port[$3C4] := $11; { Enable read/write mode } Port[$3C5] := Port[$3C5] or $80; Port[$3CE] := $0B; { Enable PR0B } Port[$3CF] := Port[$3CF] or $80; Setting PR0A and PR0B is the same as for duel paging mode. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the Trident SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the Trident SVGA Card ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ There are two Trident SVGA chips, the TVGA 8800 and 8900. The Trident SVGA chips can be identified by attempting to change the Mode Control #1 register as follows: Index : 0Eh at port 3C4h Read/write data from port 3C5h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ³ PAGE First write the value 0Eh to port 3C4h. Then read the value in from port 3C5h and save it. for rest Next write the value 00h to port 3C5h and read the value back in from the port. If bit 1 in the value read is set (ie = 1) then a trident chip is present. Finally write the original value back to port 3C5h to leave the SVGA adapter in it's original state. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which Trident Chip is Present ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The Trident chip can be identified with the following psuedo code : Port[$3C4] := $0B Port[$3C5] := $00 hardware_version_number := Port[$3C5] if hardware_version_number >= 3 then chip is an 8900 else chip is an 8800 This procedure leaves the chip in "New Mode". New Mode and Old mode are discussed below. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Trident Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors Chip ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 5Bh 800x600 16 8800/8900 ³ ³ 5Ch 640x400 256 8800/8900 ³ ³ 5Dh 640x480 256 8800/8900 ³ ³ 5Eh 800x600 256 8900 ³ ³ 5Fh 1024x768 16 8800/8900 ³ ³ 61h 768x1024 16 8800/8900 ³ ³ 62h 1024x768 256 8900 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Trident Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Both Trident chips can map video memory in either 64K or 128K paging schemes. The 8800 defaults to the 128K paging scheme at power up. This scheme is known as the "Old Mode". The 8900 defaults to the 64K paging scheme, the "New Mode". This file will concentrate solely on the 64K new mode operation. The new mode can be set with the following procedure: Port[$3C4] := $0B { Set the old mode 128K scheme } Port[$3C5] := $00 dummy_variable := Port[$3C5] { Toggle over to the new mode } Trident bank switching is weird, REALLY weird! In new mode, the New Mode Control Register # 1 is used to select the active bank: Index : 0Eh at port 3C4h Read/write data from port 3C5h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÂÄÙ ³ ³ Bank Page Seg Bits 3-0 can be considered as a single 4 bit bank number. However, when you write to video memory the Trident inverts the Page bit to determine which bank should actually be written to. So if you set these bits to the value 0 (0000) then bank 0 will be used for all read operations and bank 2 (0010) will be used for all write operations. The following code will set the bank number for all read operations: PortW[$3C4] := bank_number shl 8 + $0E; The following code will set the bank number for all write operations: PortW[$3C4] := (bank_number xor 2) shl 8 + $0E; It is important to realise that setting the write bank number changes the read bank number, and visa-versa. How you are supposed to rapidly transfer blocks of data around on the Trident screen is beyond me. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the Tseng SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the Tseng SVGA Card ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Tseng Labs have produced two SVGA Chips, the ET3000 and the ET4000. The Tseng SVGA chips can be identified by attempting to change the Miscellaneous register as follows: Index : 06h at port 3C0h Read/write data from port 3C1h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÂÄÙ High Output the value 6 to port 3C0h and read a byte from port 3C1h. Modify the high field in this byte (eg new byte = byte XOR 30h) and write this new byte to port 3C1h. Read the byte from port 3C1h and see if the byte was successfully modified, if it was then a Tseng chip is present. Having done this, write the original byte back to port 3C1h to leave the graphics adapter in it's original state. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which Tseng Card is Present ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The ET4000 can be distinguished from the ET3000 by attempting to change the ET4000 Extended Start Address register as follows: Index : 33h at port 3D4h Read/write data from port 3D5h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÂÄÙ ÀÄÂÄÙ CAD DAD The same technique is used as was used to identify the presence of a Tseng chip, both fields should be modified, written, tested for a successful write and then restored to their original values. If the change was successful an ET4000 chip is present, otherwise an ET3000 chip is. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Tseng Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 25h 640x480 16 ³ ³ 29h 800x600 16 ³ ³ 2Dh 640x350 256 ³ ³ 2Eh 640x480 256 ³ ³ 2Fh 640x400 256 ³ ³ 30h 800x600 256 ³ ³ 37h 1024x768 16 ³ ³ 38h 1024x768 256 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ All graphics modes in the above table are supported by the ET4000. I am not sure which modes are supported by the ET3000. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Tseng Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In my opinion the Tseng memory mapping was designed to prevent graphics programmers from suffering nervous breakdowns! Two banks can be mapped to the segment A000:0000-FFFFh, one for reading and one for writing. The banks can be selected by writing to the Segment Select Registers at port 3Cdh: ET3000 Segment Select Register: Port 3CDh ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÂÄÄÄÙ ÀÄÄÄÂÄÄÄÙ Read Write Bank Bank ET4000 Segment Select Register: Port 3CDh ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÄÙ Read Write Bank Bank Both of these registers can be read from as well as written to. Each bank is 64K long, has a 64K granularity and is mapped to host memory A000:0000-FFFFh. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ DPMI and the ET4000 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Apparently the ET4000 chip is capable of linear addressing in dos protect- mode programs. To enable this feature write the value 36h to port 3D4h, read the value from port 3D5h, set the lower nibble (bits 0 -> 3) to the value 1 and rewrite the value to port 3D5h. Resetting these bits to the value 0 puts the chip back in regular segmented addressing mode. I have no information where or how the entire ET4000 memory would then be mapped to linear memory. If anyone has more information on this or has a Tseng card they are willing to try it on let me know. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the Video7 SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Video7 Extensions ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ To modify any of the Video7 extended registers you must enable the extensions. Disable them once you are done. To enable extensions: PortW[$3C4] := $EA06; To disable extensions: PortW[$3C4] := $AE06; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying the Video7 SVGA Chip ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The presence of a Video7 chip can be detected with the following procedure: var old_value, new_value, id : byte; EnableVideo7Extensions; Port[$3D4] := $0C; old_value := Port[$3D5]; Port[$3D5] := $55; new_value := Port[$3D5]; Port[$3D4] := $1F; id := Port[$3D5]; Port[$3D4] := $0C; Port[$3D5] := old_value; DisableVideo7Extentions; { Check that register value is $55 Xor $EA } if id = $BF then card is a video7 else card isn't a video7 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Identifying which Video7 Chip is Present ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Once you know that the video card has a video7 in it you can read the Chip Revision register to find out which chip it is: Port[$3C4] := $8E; chip := Port[$3C5]; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Value in ³ ³ chip variable Video7 Chip ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 40h-49h 1024i ³ ³ 50h-59h V7VGA Version 5 ³ ³ 70h-7Eh V7VGA FASTWRITE/VRAM ³ ³ 80h-FFh VEGA VGA ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Video7 Graphics Display Modes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Mode Resolution Colors ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 60h 752x410 16 ³ ³ 61h 720x540 16 ³ ³ 62h 800x600 16 ³ ³ 63h 1024x768 2 ³ ³ 64h 1024x768 4 ³ ³ 65h 1024x768 16 ³ ³ 66h 640x400 256 ³ ³ 67h 640x480 256 ³ ³ 68h 720x540 256 ³ ³ 69h 800x600 256 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Video7 Display Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Remeber, the extensions must be enabled before calling any of the following procedures. The Video7 version 1-3 chips use a ridiculously complex method to switch banks (in my opinion at least), so for these chips I'll only include the code to bank switch and leave the technical info on what it does and why it does it till a future PC-GPE version (if ever). The version 1-3 chips map two banks to host memory A000:0000-FFFFh. One bank is used for read operations, the other is used for write operations. For 256 color modes there are 16 64k banks (numbered 0 - 15 for the following procedures). One really "stuffed in the head" thing about these chips (from a programmers point of view anyway) is that both bank registers use a common SVGA register to store their 2 low order bits, so if you set the Read Bank number, the Write Bank number's 2 low order bits will be set the same as the Read Bank number's 2 low order bits. The Write Bank number for 256 color modes can be set with the following procedure: Port[$3C4] := $F9; Port[$3C5] := bank_number and 1; Port[$3C2] := (Port[$3CC] and $DF) or ((bank_number and 2) shl 4); Port[$3C4] := $F6; Port[$3C5] := (Port[$3C5] and $FC) or (bank_number shr 2); The Read Bank number for 256 color modes can be set with the following procedure: Port[$3C4] := $F9; Port[$3C5] := bank_number and 1; Port[$3C2] := (Port[$3CC] and $DF) or ((bank_number and 2) shl 4); Port[$3C4] := $F6; Port[$3C5] := (Port[$3C5] and $F3) or (bank_number and $0C); By version 4 Headlands Technologies had gotten their act together and adopted a more "sane" bank switching scheme. Version 4 supports both single and duel paging schemes. There are 16 64k long banks, and a 64k granularity with the techniques used here. The single paging scheme maps a bank to host memory A000:0000-FFFFh for both read and write operations. The single paging scheme is the default for version 4, but can also be set with the following procedure: Port[$3C4] := $E0; Port[$3C5] := Port[$3C5] and $7F The Single/Write Bank Register is used to select which bank to map to host memory: Index : E8h at port 3C4h Read/Write at port 3C5h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÄÙ Bank A bank can be selected with the following procedure: PortW[$3C4] := (bank_number shl 12) + $E8; In duel paging mode one bank is mapped to A000:0000-FFFF for write operations and another for read operations. Duel paging mode can be selected with the following procedure: Port[$3C4] := $E0; Port[$3C5] := Port[$3C5] or $80; The Single/Write Bank Register (see above) is used to select which bank to map to host memory for writing operations. The Read Bank Register selects which bank to use for read operations: Index : E9h at port 3C4h Read/Write at port 3C5h ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³ 7 ³ 6 ³ 5 ³ 4 ³ 3 ³ 2 ³ 1 ³ 0 ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÄÄÂÄÄÄÄÄÙ Bank A read bank can be selected with the following procedure: PortW[$3C4] := (bank_number shl 12) + $E9; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Xtended Mode - Unchained 640x400x256 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au Please read the file SVGINTRO.TXT (Graphics/SVGA/Intro PC-GPE menu option) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ I am calling this mode Xtended mode simply because I don't know if it already has a name. It is a variation of mode x and it has worked on every SVGA I have tried it on. It seems very very unlikely that I was the first person to try this, so if this mode has already been documented elsewhere I would very much like to hear about it. Xtended mode is 640x400x256 and will only work on SVGA's supporting the "regular" 640x400x256 mode. It's advantage is that it requires no bank switching to access the entire display memory and, like mode x, polygon fill graphics can be up to 4 times faster. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Setting Xtended Mode ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Xtended mode is set similar to the way unchained mode 13h is set, the only difference is that you you call BIOS to set the 640x400x256 graphics mode instead of mode 13h. The 640x400x256 mode number varies from card to card. The following table lists the mode number for each of the 7 "standard" SVGAs: 640x400x256 mode numbers for various SVGA cards ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ SVGA Chip Mode Number ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ATI 61h ³ ³ Chips & Technologies 78h ³ ³ Genoa 7Eh ³ ³ Paradise 5Eh ³ ³ Trident 5Ch ³ ³ Tseng 2Fh ³ ³ Video 7 66h ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Alternatively the mode can be set with the VESA Set Super VGA Mode BIOS call, the VESA SVGA mode number is 100h. Refer to the file "VESASP12.TXT" for more information on VESA BIOS calls. The following Pascal procedure will set Xtended mode for a card with a VESA driver: const VIDEO = $10; { Video interrupt number } CRTC_ADDR = $3d4; { Base port of the CRT Controller (color) } SEQU_ADDR = $3c4; { Base port of the Sequencer } procedure InitXtended; begin { Set VESA 640x400x256 mode } asm mov ax, $4F02 mov bx, $100 int VIDEO end; { Turn the VGA screen off } Port[SEQU_ADDR] := 1; Port[SEQU_ADDR + 1] := Port[SEQU_ADDR + 1] or $20; { Turn off the Chain-4 bit (bit 3 at index 4, port 0x3c4) } PortW[SEQU_ADDR] := $0604; { Turn off word mode, by setting the Mode Control register of the CRT Controller (index 0x17, port 0x3d4) } PortW[CRTC_ADDR] := $E317; { Turn off doubleword mode, by setting the Underline Location register (index 0x14, port 0x3d4) } PortW[CRTC_ADDR] := $0014; { Clear entire video memory, by selecting all four planes, then writing color 0 to the entire segment. Stoopid FillChar fills 1 byte too short! } PortW[SEQU_ADDR] := $0F02; FillChar(Mem[$A000 : 0], $8000, 0); FillChar(Mem[$A000 : $8000], $8000, 0); { Give a small delay to let the screen sort itself out } Delay(100); { Turn the screen back on } Port[SEQU_ADDR] := 1; Port[SEQU_ADDR + 1] := Port[SEQU_ADDR + 1] and $DF; end; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Drawing a Pixel ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Drawing a pixel in Xtended mode is similar to drawing one in unchained mode 13h or mode x, we just have to keep in mind that the display is now twice as wide. Also keep in mind that 640x400 has 4 times as many pixels as 320x200, so there is only one page in Xtended mode. This example Pascal routine will draw a pixel at any screen position. I'll let you do the job of converting it to assembly: procedure XtendedPutPixel(x, y : word; color : byte); begin { Set map mask to select proper plane } PortW[SEQU_ADDR] := $100 shl (x and 3) + 2; { Calculate address (y * 160 + x div 4) and write pixel } Mem[$A000 : y shl 7 + y shl 5 + x shr 2] := color; end; ÖÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´% VLA Proudly Presents %ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ· º º ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Three Dimensional Rotations For Computer Graphics ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ By Lithium /VLA One of the most difficult programming difficulties you will face is that of representing a 3D world on that 2D screen in front of you. It requires some linear alegbra that may be difficult for some, so I will spend some bytes explaining the mathmatic computations of using matricies in addition to the equations to get you going. This document is intended to be an aid to anyone programming in any language, as a result it will use mathmatic notation. If you are worthy of using these routines, you ought to be able to get them into your favorite language. All I ask is that you pay a little tribute to your programming buddies in VLA. If you aren't a math person, skip to the end and get the final equations. Just be forewarned, implimenting these equations into a coherient 3D world is hard enough when you undersand the mathmatics behind them... REAL PROGRAMMERS AREN'T AFRAID OF MATH 3D Coordinates ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Just so we all understand each other, 3D is defined in of course three directions, we'll call them (X,Y,Z). X will be the horizontal plane of your screen, Z will stretch vertically, and Y will extend out of and into your screen. Got it? Hope so, becuase it gets a bit tricky now. The next system is called Sphereical Coordinates it is defined by angles and distance in (é,í,p) These Greek letters are Theta (é), Phi (í), and Roe (p) Z Z | | é - Angle in the XY | |\ plane | |\\ | | \\ í - Angle from the Z |______ X |í_|\___X axis / / \ v \ / / é \ o p - Distance to point / /\ \ | from the origin / / --> \ | (0,0,0) Y Y \| To relate the two systems you can use these equations. X = p(siní)(cosé) é = arctan (X/Y) Y = p(siní)(siné) í = arccos (Z/p) Z = p(cosí) p = û(X^2 + Y^2 + Z^2) If these don't seem right, do a couple of example problems for yourself, it should make since after a bit of trig. Matrix Notation ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Lets say I can define Xt and Yt with the equations: Xt = aX + bY Where a,b,c,d are coeffiencets Yt = cX + dY The matrix notation for this system of equations would be: Ú ¿ (Xt,Yt) = (X,Y)³a c³ ³ ³ And we solve for this with these steps ³b d³ À Ù Ú ¿ Xt = (X,Y)³a .³ = aX + bY ³ ³ We move across the coordinates left to right ³b .³ and multiply them by the coeffients in the À Ù matrix, top to bottom Ú ¿ Yt = (X,Y)³. c³ = cX + dY ³ ³ For Y, the second number, we use the second ³. d³ column of the matrix À Ù We can also multiply matricies in this fashion Ú ¿ Ú ¿ T = T1*T2 Where T1 = ³a c³ and T2 = ³e g³ ³ ³ ³ ³ ³b d³ ³f h³ À Ù À Ù Ú ¿Ú ¿ Ú ¿ ³a c³³e g³ ³(ae + cf) (ag + ch)³ rows -> columns | ³ ³³ ³ = ³ ³ v ³b d³³f h³ ³(be + df) (bg + dh)³ À ÙÀ Ù À Ù This product is dependent on position, so that means that T1*T2 *DOES NOT* equal T2*T1 In English, the process above went like this, we move left to right in the first matrix, T1, and top to bottom in the second, T2. AE + CF is our first position. The numbers in the first row are multiplied by the numbers in the first column. 1st * 1st + 2nd * 2nd is our first value for the new matrix. Then you repeat the process for the next column of the second matrix. After that, you move down to the next row of the first matrix, and multiply it by the 1st column of the second matrix. You then do the same for the next column of the second matrix. This process is repeated until you've done all of the rows and columns. If this is your introduction to matricies, don't feel bad if you're a bit confused. They are a different mode of thinking about equations. The operations above give the same results as if you were to do the long hand algebra to solve them. It may seem a bit more difficult for these examples, but when you get to systems of equations with many variables, this way is MUCH faster to compute. Trust me, especially when you make your program do it. So, now you have the basic math.... One important point for these matricies below. I will use a homogeneous coordinate system, (X/r, Y/r, Z/r, r) Now I'll use r=1, so nothing will really be different in my calculations, but you need to understand the purpose. This form is very convienent for the translations and rotation equations we will need to do because it allows for scaling of our points with respect to a center point. Consider a point (2,2,2) in an object centered at (1,1,1). If we were to scale the X direction by 3,(the X length to the center is 3 times what it was) the point we want would be (4,2,2). Our new X = 3*(OldX-CenterX). Without the added factor of the homogeneous system, calculations assume all objects are centered at the origin, so our point would have turned out to be (6,2,2), NOT the one we wanted. So that's why we are going to do it that way. ROTATIONS AND TRANSFORMATIONS ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Translation ÄÄÄÄÄÄÄÄÄÄÄ We will start with translation from the origin. Most objects are not at (0,0,0,1), so we'll call their center (Tx,Ty,Tz,1). Ú ¿ ³ 1 0 0 0³ = T1 ³ ³ ³ 0 1 0 0³ This physically moves the object, so it is centered ³ ³ at the origin for our calcuations, eliminating the ³ 0 0 1 0³ need for a -Tx for each X, the matrix will factor it ³ ³ in when we multiply it by the others ³-Tx -Ty -Tz 1³ À Ù But, we need sphereical coordinates... Ú ¿ ³ 1 0 0 0 ³ ³ ³ = T1 ³ 0 1 0 0 ³ ³ ³ ³ 0 0 1 0 ³ ³ ³ ³-p(cosé)(siní) -p(siné)(siní) -p(cosí) 1 ³ À Ù XY Clockwise Rotation ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This will be our first rotation, about the Z-Axis Ú ¿ ³ siné cosé 0 0 ³ ³ ³ = T2 ³-cosé siné 0 0 ³ ³ ³ ³ 0 0 1 0 ³ ³ ³ ³ 0 0 0 1 ³ À Ù YZ Counter-Clockwise Rotation ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Now we rotate about the X axis Ú ¿ ³ 1 0 0 0 ³ ³ ³ = T3 ³ 0 -cosí -siní 0 ³ ³ ³ ³ 0 siní -cosí 0 ³ ³ ³ ³ 0 0 0 1 ³ À Ù Notice that with two rotations that we can get any position in 3D space. Left Hand Correction ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This will flip the X coordinates. Think about when you look into the mirror, your left hand looks like your right. These rotations do the same thing, so by flipping the X, it will make your X move right when you increase it's value. Ú ¿ ³ -1 0 0 0 ³ ³ ³ = T4 ³ 0 1 0 0 ³ ³ ³ ³ 0 0 1 0 ³ ³ ³ ³ 0 0 0 1 ³ À Ù The Final Viewing Matrix ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is the net transformation matrix for our viewing perspective The math for this one is really messy, and I would need to go over even more matrix stuff to get it reduced, so I will ask you to trust my calculations V = T1*T2*T3*T4 Ú ¿ ³ -siné -(cosé)(cosí) -(cosé)(siní) 0 ³ ³ ³ = V ³ cosé -(siné)(cosí) -(siné)(siní) 0 ³ ³ ³ ³ 0 siní -cosí 0 ³ ³ ³ ³ 0 0 p 1 ³ À Ù Lets say our original (X,Y,Z,1) were just that, and the point after the rotation is (Xv,Yv,Zv,1) (Xv,Yv,Zv,1) = (X,Y,Z,1) * V Xv = -Xsiné + Ycosé Yv = -X(cosé)(cosí) - Y(siné)(cosí) + Zsiní Zv = -X(cosé)(siní) - Y(siné)(siní) - Zcosí + p ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Some people have had trouble concepts of this implimentation, so I have another way of setting up the equations. This works off of the straight X,Y, and Z coordinates too, but uses another angle. We will define the following variables Xan = Rotation about the X-Axis Yan = Rotation about the Y-Axis Zan = Rotation about the Z-Axis Rotation about the Y Axis ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ú ¿ ³ cos(Yan) 0 sin(Yan) ³ ³ ³ ³ 0 1 0 ³ ³ ³ ³ -sin(Yan) 0 cos(Yan) ³ À Ù Rotation about the Z Axis ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ú ¿ ³ 1 0 0 ³ ³ ³ ³ 0 cos(Zan) -sin(Zan) ³ ³ ³ ³ 0 sin(Zan) cos(Zan) ³ À Ù Rotation about the X Axis ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ú ¿ ³ cos(Xan) -sin(Xan) 0 ³ ³ ³ ³ sin(Xan) cos(Xan) 0 ³ ³ ³ ³ 0 0 1 ³ À Ù For simplification, lets call sin(Yan) = s1, cos(Xan) = c3, sin(Zan) = s2, etc Final Rotation Matrix ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ú ¿ ³ c1c3 + s1s2s3 -c1s3 + c3s1s2 c2s1 ³ ³ ³ ³ c2s3 c2c3 -s2 ³ ³ ³ ³ -c3s1 + c1s2s3 s1s3 + c1c3s2 c1c2 ³ À Ù ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Xv = x(s1s2s3 + c1c3) + y(c2s3) + z(c1s2s3 - c3s1) Yv = x(c3s1s2 - c1s3) + y(c2c3) + z(c1c3s2 + s1s3) Zv = x(c1s2s3 - c3s1) + y(-s2) + z(c1c2) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Where Xv,Yv, and Zv are the final rotated points and the little x,y,z are the original points. Normal Vectors - The Secret To Shading and Plane Elimination ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ So, now you have the rotation equations... But, how do we make it fast? Well, one of the best optimizations you can impliment is plane elimination. It boils down to not displaying the planes that won't be seen. With that said, here comes more math.... BE A MAN, KNOW YOUR NORMALS A 'normal' vector is perpendicular to a plane. Imagine the face of a clock as a plane. Take your right hand and point your thumb toward yourself and the other end toward the clock. Now curl your fingers in the counter-clockwise direction. Your thumb is pointing in the direction of the normal vector. This is called 'The Right Hand Rule' and it is the basis for figuring the facing of planes. A plane can be determined with three points, try it. That's the minimum you need, so that's what we will base our process on. Now if we have a line segment, we could move it to the origin, maintaining it's direction and lenght by subtracting the (X,Y,Z) of one of the points from both ends. This is our definition of a vector. A line segment, starting at the origin and extending in the direction (X,Y,Z). Here will be our plane, built from the three points below. (X1,Y1,Z1) V = (X1-X2, Y1-Y2, Z1-Z2) (X2,Y2,Z2) W = (X1-X3, Y1-Y3, Z1-Z3) (X3,Y3,Z3) So, we have our three points that define a plane. From these points we create two vectors V and W. Now if you where to use the right hand rule with these vectors, pointing your fingers in the direction of V and curling them toward the direction of W, you would have the direction of the Normal vector. This vector is perpendicular to both vectors, and since we have defined the plane by these vectors, the normal is perpendicular to the plane as well. The process of finding the normal vector is called the 'Cross Product' and it is of this form: Ú ¿ V*W =³ i k j ³ ³ ³ ³ X1-X2 Y1-Y2 Z1-Z2 ³ ³ ³ ³ X1-X3 Y1-Y3 Z1-Z3 ³ À Ù i = (Y1-Y2)(Z1-Z3) - (Z1-Z2)(Y1-Y3) -k = (Z1-Z2)(X1-X3) - (X1-X2)(Z1-Z3) j = (X1-X2)(Y1-Y3) - (Y1-Y2)(X1-X3) The Normal to the plane is (i,-k,j) NOTE: V*W *DOESN'T* equal W*V, it will be pointing in the negative direction To prove that to yourself, lets go back to how I explained it before We pointed in the direction of V and curled our fingers toward W, the normal vector in the direction of your thumb. Try it in the direction of W, toward V. It should be in the opposite direction. Your normal, still perpendicular to both vectors, but it is negative. If you use in your program, you will have the planes appearing when they shouldn't and dissapearing when they are coming into view. So, now that we have a way to determin the direction of the plane, how do we hide the plane? If the angle between the view point and the normal is greater than 90 degrees, don't show it. One quick way that I always use is to place the view point on an axis. I tipically set the Z axis to come out of the screen, Y up and X across. Set the view point to be at a positive point on the Z and then, if that normal vector has Z greater than zero, I display it, otherwise I skip to the next one. This also has an application in shading. If you define a light scource, just like the view point, you find the angle the normal and the light form. Since you don't usually just want two colors, our 90 degree trick won't work for you, but by finding this angle, and dividing all of the possible angles by the number of colors you will allow in the shading, that color can be assigned to the plane and, presto-chango, it looks like you know what your doing... As you do your rotations, just rotate the coordinates of the normal and that will keep everything updated. Tips To Speed-Up Your Routines ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Pre-Calculate as many values as possible The main limitation you will have is the speed of your math, using precalculated values like Normals, Sin/Cos charts, and distance from the origin are all good candidates. If you can get away with using a math-coprocessor, well... This will greatly increase the speed of your routine. Unfortunately, not everyone has one. Only figure values once If you multiply (Siné)(Cosé) and will use that same value later, by all means, keep it and use it then instead of doing the multiplication again. Another thing to keep in mind The order of rotations *DOES* make a difference. Try it out and you'll understand. Also, when you start to use these routines, you'll find yourself making arrays of points and plane structures. Counter-Clockwise points Be sure to list your points for the planes in counter-clockwise order. If you don't, not all of your planes will display correctly when you start hiding planes. And as always, be clever Just watch out, because when you have clever ideas you can lose a foot. My brother once had a clever idea to cut his toe nails with an axe and he lost his foot. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Books to look for... ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Any math book, the topics I covered will be found in: Normal Vectors - Analytic Geometry Matrix Operations - Linear Algebra Sines and Cosines - Trigonometry The Art of Graphics, by Jim McGregor and Alan Watt 1986 Addison-Wesley Publishers Read the VLA.NFO file to find out how to contact us. ÖÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´% VLA Proudly Presents %ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ· º º ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Three Dimensional Shading In Computer Graphics ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ By Lithium /VLA Hopefully you have read the companion document 3DROTATE.DOC, as this one will build apon the concepts presented in my attempt to teach some of the math need to make 3D graphics a reality. This file will cover such important topics as the Dot Product and how routines are best constructed for real-time 3D rotations and planar shading. Our Friend, The Dot Product The Dot Product is a neat relation that will allow you to quickly find the angle between any two vectors. It's easiest to explain graphicly, so I will exercise my extended-ASCII keys. Two Vectors A & B A (Xa, Ya, Za) ³A³ = û( (Xa)ý + (Ya)ý + (Za)ý ) B (Xb, Yb, Zb) ³B³ = û( (Xb)ý + (Yb)ý + (Zb)ý ) Where Xa, and the others coorispond to some value on their respective Axis's ¿A / / / / \ é <-- Angle Theta between vector A and B \ \ \ ÙB Cos(é) = Xa * Xb + Ya * Yb + Za * Zb ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³A³*³B³ Example: A (1,2,3) ³A³ = û( 1ý + 2ý + 3ý) = û(14) = 3.7417 B (4,5,6) ³b³ = û( 4ý + 5ý + 6ý) = û(77) = 8.7750 Cos(é) = 1 * 4 + 2 * 5 + 3 * 6 = 4 + 10 + 18 = 32 = 0.9746 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄ (3.7417)*(8.7750) 32.8334 32.8334 ArcCos (.9746) = 12.9ø So, your wondering how this revolutionizes you code, huh? Well, remember our other friend, the Normal vector? You use Normal vectors that define the directions of everything in our 3D world. Let's say that vector A was the Normal vector from my plane, and B is a vector that shows the direction that the light in my scene is pointing. If I do the Dot Product of them, you will get the angle between them, if that angle is >= 90ø and <= 270ø then no light falls on the visible surface and it doesn't need to be displayed. Also notice, the way the values of the Cosine orient themselves 90ø Cos 000ø = 1 Cos 090ø = 0 ³ Cos 180ø = -1 Negative ³ Positive Cos 270ø = 0 ³ ³ 180ø ÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ 0ø An angle between a light and a plane that ³ is less than 90ø or greater than 270ø will ³ be visible, so you can check if the Cos(é) Negative ³ Positive is greater than 0 to see if it is visible. ³ ³ 270ø How Do You Implement The Code? Easy As ã. Examples in ASM structures We will define our points like this STRUC XYZs Xpos dd ? Ypos dd ? Zpos dd ? Dist dd ? ENDS XYZs ;size is 16 bytes The X,Y,Zpos define a point in 3D space, Dist is the distance from the origin Dist = û( Xý + Yý + Zý ) Precalculate these values and have them handy in your data area Our planes should look something like this STRUC PlaneSt NumPts db ? ;3 or 4 NormIndex dw ? PtsIndex dw ? dw ? dw ? dw ? ENDS PlaneSt The number of points that in the plane depends on the number your fill routines can handle you must have at least 3 and more than 6 is not suggested Then we set up our data like this MaxPoints = 100 MaxPlanes = 100 PointList XYZs MaxPoints DUP() PlaneList PlaneSt MaxPlanes DUP() NormalList XYZs <0,0,0, 10000h> , MaxPlanes DUP() Non-ASM User Note: I set up points in a structure that had an X,Y,Z and Distance value. I set up a plane structure that had the number of points the index number of the normal vector for that plane and the index numbers for the points in the plane. The next lines set up arrays of these points in PointList, and the number of points was defined as MaxPoints. An array of planes was created as PlaneList with MaxPlanes as the total number of plane structures in the array. NormalList is an array of the vectors that are normal to the planes, one is set up initally (I'll explain that next) and then one for each possible plane is allocated. You'll notice that I defined the first Normal and then created space for the rest of the possible normals. I'll call this first normal, the Zero Normal. It will have special properties for planes that don't shade and are never hidden. Well, before I start telling all the tricks to the writting code, let me make sure a couple of points are clear. - In the 3DROTATE.DOC I said that you could set your view point on the Z-Axis and then figure out if planes were visible by the post-rotation Normal vectors, if their Z was > 0 then display, if not, don't That is an easy way to set up the data, and I didn't feel like going into the Dot Product at the time, so I generalized. So, what if you don't view your plane from the Z-Axis, the answer is you use the... Dot Product! that's right. The angle will be used now to figure wheither or not to display the plane. - I have been mentioning lights and view points as vectors that I can use with the Normal vector from my plane. To work correctly, these vectors for the lights and view should point in the direction that you are looking or the direction that the light is pointing, *NOT* a vector drawn from the origin to the viewer position or light position. - True Normal vectors only state a direction, and should therefore have a unit distance of 1. This will have the advantage of simplifying the math involved to figure you values. Also, for God's sake, pre-compute your normal, don't do this everytime. Just rotate them when you do your points and that will update their direction. If the Normal's have a length of 1 then ³A³*³B³ = 1 * 1 = 1 So: Cos(é) = Xa * Xb + Ya * Yb + Za * Zb ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³A³*³B³ Is Reduced To: Cos(é) = Xa * Xb + Ya * Yb + Za * Zb We eliminated a multiply and a divide! Pat yourself on the back. - You ASM users might be wondering why I defined my Zero Normal as: <0,0,0,10000h> How does 10000h = a length of 1 ? Well, this is a trick you can do in ASM, instead of using floating point values that will be slow on computers without math co-processors, we can use a double word to hold our value. The high word holds the integer value, and the low word is our decimal. You do all of your computations with the whole register, but only pull the high word when you go to display the point. So, with that under consideration, 10000h = 1.00000 Not bad for integers. - How does the Zero Normal work? Since the X,Y,and Z are all 0, the Cos(é) = 0, so if you always display when Cos(é) = 0, then that plane will always be seen. So, Beyond The Babble... How To Set Up Your Code Define Data Points, Normals, and Planes Pre-Calculate as many values as possible Rotate Points and Normals Determin Visible Planes With Dot Product (Save this value if you want to shade) Sort Visible Planes Back to Front (Determin Shade From Dot Product) Clip Plane to fit scene Draw to the screen Change Angles Goto Rotation A quick way to figure out which color to shade your plane if you are using the double word values like I described before is to take the Dot Product result, it will lie between 10000h - 0h if you would like say 16 shades over the angles, then take that value and shr ,12 that will give you a value from 0h - 10h (0-16, or 17 colors) if you make 10h into 0fh, add that offset to a gradient in your palette, then you will have the color to fill your polygon with. Note also that the Cosine function is weighted toward the extremes. If you want a smooth palette change as the angles change, your palette should weight the gradient accordingly. A useful little relation for depth sorting is to be able to find the center of a triangle. E The center C = (D + E + F)/3 ^ / \ Divide each cooridinate by (Xd + Xe + Xf)/3 = Xc / C \ and do the same for the Y's and Z's if you / \ choose to sort with this method. Then rotate DÄÄÄÄÄÄÄÄÄF that point and use it to depth sort the planes Phong and Goraud Shading Recently, someone asked me about the practiblity of real-time phong and goraud shading. The technique is common to ray-tracers and requires a great deal of calculation when working with individual rays cast from each pixel, but when only using this for each plane, it is possible. This type of shading involves taking into account the reduced luminousity of light as distance increases. For each light, you define a falloff value. This value should be the distance a which the light will be at full intensity. Then at 2*FallOff you will have 1/2 intensity, 3*FallOff will yeild 1/3 and so on. To implement this type of shading, you will need to determin the distance from the light to the center of the plane. If distance < FallOff, then use the normal intensity. If it is greater, divide the FallOff value by the distance. This will give you a scalar value that you can multiple by the shading color that the plane should have. Use that offset and it will be darker since it is further away from the light source. However, to determin the distance form the light to each plane, you must use a Square Root function, these are inherently slow unless you don't care about accuracy. Also, it would be difficult to notice the use of this technique unless you have a relatively small FallOff value and your objects move about in the low intesity boundries. Well, that's all that I feel like doing tonight, and besides, Star Trek is on! So, see VLA.NFO for information about contacting myself or any of the other members of VLA. Happy Coding! ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Perspective Transforms ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ By Andre Yew (andrey@gluttony.ugcs.caltech.edu) This is how I learned perspective transforms --- it was intuitive and understandable to me, so perhaps it'll be to others as well. It does require knowledge of matrix math and homogeneous coordinates. IMO, if you want to write a serious renderer, you need to know both. First, let's look at what we're trying to do: S (screen) | * P (y, z) | /| | / | | / | |/ | * R | / | | / | | / | | E (eye)/ | | W ---------*-----|----*------------- <- d -><-z-> E is the eye, P is the point we're trying to project, and R is its projected position on the screen S (this is the point you want to draw on your monitor). Z goes into the monitor (left- handed coordinates), with X and Y being the width and height of the screen. So let's find where R is: R = (xs, ys) Using similar triangles (ERS and EPW) xs/d = x/(z + d) ys/d = y/(z + d) (Use similar triangles to determine this) So, xs = x*d/(z + d) ys = y*d/(z + d) Express this homogeneously: R = (xs, ys, zs, ws). Make xs = x*d ys = y*d zs = 0 (the screen is a flat plane) ws = z + d and express this as a vector transformed by a matrix: [x y z 1][ d 0 0 0 ] [ 0 d 0 0 ] = R [ 0 0 0 1 ] [ 0 0 0 d ] The matrix on the right side can be called a perspective transform. But we aren't done yet. See the zero in the 3rd column, 3rd row of the matrix? Make it a 1 so we retain the z value (perhaps for some kind of Z-buffer). Also, this isn't exactly what we want since we'd also like to have the eye at the origin and we'd like to specify some kind of field-of-view. So, let's translate the matrix (we'll call it M) by -d to move the eye to the origin: [ 1 0 0 0 ][ d 0 0 0 ] [ 0 1 0 0 ][ 0 d 0 0 ] [ 0 0 1 0 ][ 0 0 1 1 ] <--- Remember, we put a 1 in (3,3) to [ 0 0 -d 1 ][ 0 0 0 d ] retain the z part of the vector. And we get: [ d 0 0 0 ] [ 0 d 0 0 ] [ 0 0 1 1 ] [ 0 0 -d 0 ] Now parametrize d by the angle PEW, which is half the field-of-view (FOV/2). So we now want to pick a d such that ys = 1 always and we get a nice relationship: d = cot( FOV/2 ) Or, to put it another way, using this formula, ys = 1 always. Replace all the d's in the last perspective matrix and multiply through by sin's: [ cos 0 0 0 ] [ 0 cos 0 0 ] [ 0 0 sin sin ] [ 0 0 -cos 0 ] With all the trig functions taking FOV/2 as their arguments. Let's refine this a little further and add near and far Z-clipping planes. Look at the lower right 2x2 matrix: [ sin sin ] [-cos 0 ] and replace the first column by a and b: [ a sin ] [ b 0 ] [ b 0 ] Transform out near and far boundaries represented homogeneously as (zn, 1), (zf, 1), respectively and we get: (zn*a + b, zn*sin) and (zf*a + b, zf*sin). We want the transformed boundaries to map to 0 and 1, respectively, so divide out the homogeneous parts to get normal coordinates and equate: (zn*a + b)/(zn*sin) = 0 (near plane) (zf*a + b)/(zf*sin) = 1 (far plane) Now solve for a and b and we get: a = (zf*sin)/(zf - zn) = sin/(1 - zn/zf) b = -a*zn b = -a*zn At last we have the familiar looking perspective transform matrix: [ cos( FOV/2 ) 0 0 0 ] [ 0 cos( FOV/2 ) 0 0 ] [ 0 0 sin( FOV/2 )/(1 - zn/zf) sin( FOV/2 ) ] [ 0 0 -a*zn 0 ] There are some pretty neat properties of the matrix. Perhaps the most interesting is how it transforms objects that go through the camera plane, and how coupled with a clipper set up the right way, it does everything correctly. What's interesting about this is how it warps space into something called Moebius space, which is kind of like a fortune-cookie except the folds pass through each other to connect the lower folds --- you really have to see it to understand it. Try feeding it some vectors that go off to infinity in various directions (ws = 0) and see where they come out. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Bresenham's Line and Circle Algorithms ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Bresenham is a pretty smart cookie (note the use of the word "is", last I heard he was still working for IBM). This file contains the algorithms he developped for drawing lines and circles on a pixelated display system such as the VGA. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Line Algorithm ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The basic algorithm works for lines which look like this: o------- ¿ p1 -------- ³ deltay ------- p2 ³ -------o Ù ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ deltax where p1 = (x1,y1), p2 = (x2, y2), x and y are both increasing from p1 to p2, deltax = x2 - x1, deltay = y2 - y1 and deltax >= deltay. All other types of lines can be derived from this type. I'll get to this bit later. First you need to perform the following intialisation: x = x1 y = y1 d = (2 * deltay) - deltax x is the current x location, you will add 1 to this variable after every pixel you draw until all pixels have been drawn. y is the current y location. The decision variable is used to determine when to add 1 to this value. d is the decision variable which will be used to keep a track of what to do. Now you loop across the screen from x1 to x2 and for each loop perform the following operations for each pixel : PutPixel(x, y); { Draw a pixel at the current point } if d < 0 then d := d + (2 * deltay) else begin d := d + 2 * (deltay - deltax); y := y + 1; end; x := x + 1; It's that simple! ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Speeding Up The Line Algorithm ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ There are several useful techniques for speeding up Bresenhams line algorithm. For starters, notice that all multiplications are by 2. This can be performed with a simple shift left instruction (Shl in Pascal, << in C). Next notice that the values you add to the decision variable do not change throughout the loop, so they can be precalculated beforehand. One property of lines is that they are symetrical about their mid-points, and we can use this property to speed up the algorithm. Store two x and y values, (xa, ya) and (xb, yb). Have each pair start on either end of the line. For each pass through the loop you draw the pixel at both points, add 1 to xa and subtract one from xb. When d >= 0 add 1 to ya and subtract one from yb. You then only need to loop until xa = xb. It's also obvious that if the decision variable becomes the same value it was when it was initialised, then the rest of the line is just copies of the line you have already drawn up to that point. You might be able to speed the algorithm up by keeping an array of how y has been modified and then use this array if the line starts repeating itself. If you are using the Intel registers to store all values then you probably wouldn't get much of a speed increase (in fact it could slow it down), but it would probably be useful for thing like linear texture mapping (discussed below). I've never actually tried implementing this technique, and I would like to hear the results if anyone does. Above all remember that these optimisations will only significantly speed up the line drawing algorithm if the whole thing is done in assembly. A profile of the example program at the end of this file showed that 40% of CPU time was spent in the slow PutPixel routine I was using, the loop mechanics and testing the sign of the decision variable. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Other Uses for the Line Algorithm ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ A line can be represented by the equation y = mx + c, where m = deltay / deltax. Note that this is a version of the standard linear equation ax + bx + c = 0. There are many algorithms which use this equation. One good use for the bresenham line algorithm is for quickly drawing filled concave polygons (eg triangles). You can set up an array of minimum and maximum x values for every horizontal line on the screen. You then use bresenham's algorithm to loop along each of the polygon's sides, find where it's x value is on every line and adjust the min and max values accordingly. When you've done it for every line you simply loop down the screen drawing horizontal lines between the min and max values for each line. Another area is in linear texture mapping (see the PC-GPE article on texture mapping). This method involves taking a string of bitmap pixels and stretching them out (or squashing them in) to a line of pixels on the screen. Typically you would draw a vertical line down the screen and use Bresenhams to calculate which bitmap pixel should be drawn at each screen pixel. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Circle Algorithm ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Circles have the property of being highly symetrical, which is handy when it comes to drawing them on a display screen. |y (This diagram is supposed to be a circle, try viewing | it in 50 line mode). \ ..... / . | . We know that there are 360 degrees in a circle. First we . \ | / . see that a circle is symetrical about the x axis, so . \|/ . only the first 180 degrees need to be calculated. Next --.---+---.-- we see that it's also symetrical about the y axis, so now . /|\ . x we only need to calculate the first 90 degrees. Finally . / | \ . we see that the circle is also symetrical about the 45 . | . degree diagonal axis, so we only need to calculate the / ..... \ first 45 degrees. | | Bresenhams circle algorithm calculates the locations of the pixels in the first 45 degrees. It assumes that the circle is centered on the origin. So for every pixel (x,y) it calculates we draw a pixel in each of the 8 octants of the circle : PutPixel(CenterX + X, Center Y + Y) PutPixel(CenterX + X, Center Y - Y) PutPixel(CenterX - X, Center Y + Y) PutPixel(CenterX - X, Center Y - Y) PutPixel(CenterX + Y, Center Y + X) PutPixel(CenterX + Y, Center Y - X) PutPixel(CenterX - Y, Center Y + X) PutPixel(CenterX - Y, Center Y - X) So let's get into the actual algorithm. Given a radius for the circle we perform this initialisation: d := 3 - (2 * RADIUS) x := 0 y := RADIUS Now for each pixel we do the following operations: Draw the 8 circle pixels if d < 0 then d := d + (4 * x) + 6 else begin d := d + 4 * (x - y) + 10 y := y - 1; end; And we keep doing this until x = y. Note that the values added to the decision variable in this algorithm (x and y) are constantly changing, so we cannot precalculate them. The muliplications however are by 4, and we can accomplish this by shifting left twice. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ A Pascal General Line Procedure ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The basic bresenham line algorithm can be modified to handle all types of lines. In this section assume that deltax = abs(x2 - x1) and deltay = abs(y2 - y1). First let's take lines where deltax >= deltay. Now if x1 > x2 then you will need to subtract 1 from x for every pass through the loop. Similarly if y1 > y2 then you will be also need to subtract 1 from y for every pass through the loop where d < 0. Lines where deltax < deltay can be handled the same way, you just swap all the deltax's and deltay's around. The fastest method of handling all cases is to write a custom routine for each of the 8 line types: 1) x1 <= x2, y1 <= y2, deltax >= deltay 2) x1 <= x2, y1 <= y2, deltax < deltay 3) x1 <= x2, y1 > y2, deltax >= deltay 4) x1 <= x2, y1 > y2, deltax < deltay 5) x1 > x2, y1 <= y2, deltax >= deltay 6) x1 > x2, y1 <= y2, deltax < deltay 7) x1 > x2, y1 > y2, deltax >= deltay 8) x1 > x2, y1 > y2, deltax < deltay This will give you the fastest results, but will also make your code 8 times larger! Alternatively you can declare a few extra variables and use a common inner loop for all lines: numpixels = number of pixels to draw = deltax if deltax >= deltay or = deltay if deltax < deltay dinc1 = the amount to add to d when d < 0 dinc2 = the amount to add to d when d >= 0 xinc1 = the amount to add to x when d < 0 xinc2 = the amount to add to x when d >= 0 yinc1 = the amount to add to y when d < 0 yinc2 = the amount to add to y when d >= 0 The following is a simple example program which uses this technique: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ { BRESLINE.PAS - A general line drawing procedure. By Mark Feldman This is a very simple implementation of bresenhams' line algorithm with no optimisations. It can draw about 6000 random lines a second in mode 13h on my 486SX33 with sloooooow Paradise Extended VGA. } procedure Line(x1, y1, x2, y2 : integer; color : byte); var i, deltax, deltay, numpixels, d, dinc1, dinc2, x, xinc1, xinc2, y, yinc1, yinc2 : integer; begin { Calculate deltax and deltay for initialisation } deltax := abs(x2 - x1); deltay := abs(y2 - y1); { Initialize all vars based on which is the independent variable } if deltax >= deltay then begin { x is independent variable } numpixels := deltax + 1; d := (2 * deltay) - deltax; dinc1 := deltay Shl 1; dinc2 := (deltay - deltax) shl 1; xinc1 := 1; xinc2 := 1; yinc1 := 0; yinc2 := 1; end else begin { y is independent variable } numpixels := deltay + 1; d := (2 * deltax) - deltay; dinc1 := deltax Shl 1; dinc2 := (deltax - deltay) shl 1; xinc1 := 0; xinc2 := 1; yinc1 := 1; yinc2 := 1; end; { Make sure x and y move in the right directions } if x1 > x2 then begin xinc1 := - xinc1; xinc2 := - xinc2; end; if y1 > y2 then begin yinc1 := - yinc1; yinc2 := - yinc2; end; { Start drawing at } x := x1; y := y1; { Draw the pixels } for i := 1 to numpixels do begin PutPixel(x, y, color); if d < 0 then begin d := d + dinc1; x := x + xinc1; y := y + yinc1; end else begin d := d + dinc2; x := x + xinc2; y := y + yinc2; end; end; end; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Note that if you are writing a line routine for mode 13h (for example) you can speed it up by converting the inner loop to assembly and including mode 13h specific code. This portion of the above routine works the same but the values are stored in a single variable (screen) which holds the memory address of the current pixel, screeninc1 and screeninc2 are the update values for screen. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ var screen : word; screeninc1, screeninc2 : integer; . . . { Start drawing at } screen := word(y1) * 320 + x1; screeninc1 := yinc1 * 320 + xinc1; screeninc2 := yinc2 * 320 + xinc2; { Draw the pixels } asm { Use as many registers as are available } push $A000 pop es mov di, screen mov dx, d mov al, color mov cx, numpixels mov bx, dinc1 @bres1: { Draw the current pixel and compare the decision variable to 0 } mov es:[di], al cmp dx, 0 jnl @bres2 { D < 0 } add dx, bx { bx = dinc1 } add di, screeninc1 jmp @bres3 @bres2: { D >= 0 } add dx, dinc2 add di, screeninc2 @bres3: loop @bres1 end; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ A General Conics Sections Scan Line Algorithm ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The following code is the complete algorithm for the general conic drawer as mentioned in Foley/VanDam. It is included here with the permission of Andrew W. Fitzgibbon, who derived the remaining code sections not included in the book. // // CONIC 2D Bresenham-like conic drawer. // CONIC(Sx,Sy, Ex,Ey, A,B,C,D,E,F) draws the conic specified // by A x^2 + B x y + C y^2 + D x + E y + F = 0, between the // start point (Sx, Sy) and endpoint (Ex,Ey). // Author: Andrew W. Fitzgibbon (andrewfg@ed.ac.uk), // Machine Vision Unit, // Dept. of Artificial Intelligence, // Edinburgh University, // // Date: 31-Mar-94 #include #include #include static int DIAGx[] = {999, 1, 1, -1, -1, -1, -1, 1, 1}; static int DIAGy[] = {999, 1, 1, 1, 1, -1, -1, -1, -1}; static int SIDEx[] = {999, 1, 0, 0, -1, -1, 0, 0, 1}; static int SIDEy[] = {999, 0, 1, 1, 0, 0, -1, -1, 0}; static int BSIGNS[] = {99, 1, 1, -1, -1, 1, 1, -1, -1}; int debugging = 1; struct ConicPlotter { virtual void plot(int x, int y); }; struct DebugPlotter : public ConicPlotter { int xs; int ys; int xe; int ye; int A; int B; int C; int D; int E; int F; int octant; int d; void plot(int x, int y); }; void DebugPlotter::plot(int x, int y) { printf("%3d %3d\n",x,y); if (debugging) { // Translate start point to origin... float tF = A*xs*xs + B*xs*ys + C*ys*ys + D*xs + E*ys + F; float tD = D + 2 * A * xs + B * ys; float tE = E + B * xs + 2 * C * ys; float tx = x - xs + ((float)DIAGx[octant] + SIDEx[octant])/2; float ty = y - ys + ((float)DIAGy[octant] + SIDEy[octant])/2; // Calculate F float td = 4*(A*tx*tx + B*tx*ty + C*ty*ty + tD*tx + tE*ty + tF); fprintf(stderr,"O%d ", octant); if (d<0) fprintf(stderr," Inside "); else fprintf(stderr,"Outside "); float err = td - d; fprintf(stderr,"Real(%5.1f,%5.1f) = %8.2f Recurred = %8.2f err = %g\n", tx, ty, td/4, d/4.0f, err); if (fabs(err) > 1e-14) abort(); } } inline int odd(int n) { return n&1; } inline int abs(int a) { if (a > 0) return a; else return -a; } int getoctant(int gx, int gy) { // Use gradient to identify octant. int upper = abs(gx)>abs(gy); if (gx>=0) // Right-pointing if (gy>=0) // Up return 4 - upper; else // Down return 1 + upper; else // Left if (gy>0) // Up return 5 + upper; else // Down return 8 - upper; } int conic(int xs, int ys, int xe, int ye, int A, int B, int C, int D, int E, int F, ConicPlotter * plotterdata) { A *= 4; B *= 4; C *= 4; D *= 4; E *= 4; F *= 4; // Translate start point to origin... F = A*xs*xs + B*xs*ys + C*ys*ys + D*xs + E*ys + F; D = D + 2 * A * xs + B * ys; E = E + B * xs + 2 * C * ys; // Work out starting octant int octant = getoctant(D,E); int dxS = SIDEx[octant]; int dyS = SIDEy[octant]; int dxD = DIAGx[octant]; int dyD = DIAGy[octant]; int bsign = BSIGNS[octant]; int d,u,v; switch (octant) { case 1: d = A + B/2 + C/4 + D + E/2 + F; u = A + B/2 + D; v = u + E; break; case 2: d = A/4 + B/2 + C + D/2 + E + F; u = B/2 + C + E; v = u + D; break; case 3: d = A/4 - B/2 + C - D/2 + E + F; u = -B/2 + C + E; v = u - D; break; case 4: d = A - B/2 + C/4 - D + E/2 + F; u = A - B/2 - D; v = u + E; break; case 5: d = A + B/2 + C/4 - D - E/2 + F; u = A + B/2 - D; v = u - E; break; case 6: d = A/4 + B/2 + C - D/2 - E + F; u = B/2 + C - E; v = u - D; break; case 7: d = A/4 - B/2 + C + D/2 - E + F; u = -B/2 + C - E; v = u + D; break; case 8: d = A - B/2 + C/4 + D - E/2 + F; u = A - B/2 + D; v = u - E; break; default: fprintf(stderr,"FUNNY OCTANT\n"); abort(); } int k1sign = dyS*dyD; int k1 = 2 * (A + k1sign * (C - A)); int Bsign = dxD*dyD; int k2 = k1 + Bsign * B; int k3 = 2 * (A + C + Bsign * B); // Work out gradient at endpoint int gxe = xe - xs; int gye = ye - ys; int gx = 2*A*gxe + B*gye + D; int gy = B*gxe + 2*C*gye + E; int octantcount = getoctant(gx,gy) - octant; if (octantcount <= 0) octantcount = octantcount + 8; fprintf(stderr,"octantcount = %d\n", octantcount); int x = xs; int y = ys; while (octantcount > 0) { if (debugging) fprintf(stderr,"-- %d -------------------------\n", octant); if (odd(octant)) { while (2*v <= k2) { // Plot this point ((DebugPlotter*)plotterdata)->octant = octant; ((DebugPlotter*)plotterdata)->d = d; plotterdata->plot(x,y); // Are we inside or outside? if (d < 0) { // Inside x = x + dxS; y = y + dyS; u = u + k1; v = v + k2; d = d + u; } else { // outside x = x + dxD; y = y + dyD; u = u + k2; v = v + k3; d = d + v; } } d = d - u + v/2 - k2/2 + 3*k3/8; // error (^) in Foley and van Dam p 959, "2nd ed, revised 5th printing" u = -u + v - k2/2 + k3/2; v = v - k2 + k3/2; k1 = k1 - 2*k2 + k3; k2 = k3 - k2; int tmp = dxS; dxS = -dyS; dyS = tmp; } else { // Octant is even while (2*u < k2) { // Plot this point ((DebugPlotter*)plotterdata)->octant = octant; ((DebugPlotter*)plotterdata)->d = d; plotterdata->plot(x,y); // Are we inside or outside? if (d > 0) { // Outside x = x + dxS; y = y + dyS; u = u + k1; v = v + k2; d = d + u; } else { // Inside x = x + dxD; y = y + dyD; u = u + k2; v = v + k3; d = d + v; } } int tmpdk = k1 - k2; d = d + u - v + tmpdk; v = 2*u - v + tmpdk; u = u + tmpdk; k3 = k3 + 4*tmpdk; k2 = k1 + tmpdk; int tmp = dxD; dxD = -dyD; dyD = tmp; } octant = (octant&7)+1; octantcount--; } // Draw final octant until we reach the endpoint if (debugging) fprintf(stderr,"-- %d (final) -----------------\n", octant); if (odd(octant)) { while (2*v <= k2 && x != xe && y != ye) { // Plot this point ((DebugPlotter*)plotterdata)->octant = octant; ((DebugPlotter*)plotterdata)->d = d; plotterdata->plot(x,y); // Are we inside or outside? if (d < 0) { // Inside x = x + dxS; y = y + dyS; u = u + k1; v = v + k2; d = d + u; } else { // outside x = x + dxD; y = y + dyD; u = u + k2; v = v + k3; d = d + v; } } } else { // Octant is even while ((2*u < k2) && (x != xe) && (y != ye)) { // Plot this point ((DebugPlotter*)plotterdata)->octant = octant; ((DebugPlotter*)plotterdata)->d = d; plotterdata->plot(x,y); // Are we inside or outside? if (d > 0) { // Outside x = x + dxS; y = y + dyS; u = u + k1; v = v + k2; d = d + u; } else { // Inside x = x + dxD; y = y + dyD; u = u + k2; v = v + k3; d = d + v; } } } return 1; } main(int argc, char ** argv) { DebugPlotter db; db.xs = -7; db.ys = -19; db.xe = -8; db.ye = -8; db.A = 1424; db.B = -964; db.C = 276; db.D = 0; db.E = 0; db.F = -40000; conic(db.xs,db.ys,db.xe,db.ye,db.A,db.B,db.C,db.D,db.E,db.F, &db); } ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ A Simple Explanation of BSP Trees ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ BSP Trees ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ Binary Space Partition trees are handy for drawing 3D scenes where the positions of objects are fixed and the user's viewing coordinate changes (flight simulators being a classic example). BSP's are an extention of the "Painter's Algorithm". The painter's algorithm works by drawing all the polygons (or texture maps) in a scene in back-to- front order, so that polygon's in the background are drawn first, and polygons in the foreground are drawn over them. The "classic" painter's algorithm does have a few problems however: 1) polygon's will not be drawn correctly if they pass through any other polygon 2) it's difficult and computationally expensive calculating the order that the polygons should be drawn in for each frame 3) the algorithm cannot handle cases of cyclic overlap such as the following : ___ ___ | | | | __| |_____|___|___ | | | | |__| |_____________| | | | | __|___|_____| |___ | | | | |____________| |___| | | | | |___| |___| In this case it doesn't matter which order you draw the polygon's it still won't look right! BSP's help solve all these problems. Ok, so let's get down to business. BSP's work by building an ordered tree of all the objects in a scene. Let's imagine we live in a 2D world and we have a scene like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ³ line 1 ³ ³ \ ³ ³ \ ³ ³ \ line 2 ³ ³ \ ³ ³ \ ³ ³ ÄÄÄÄÄÄÄÄ \ ³ ³ line 3 \ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ^ viewpoint (assume the viewpoint is the origin for this example) Now if we were to draw this scene using the painters algorithm we would draw line 1 first, then line 2, finally line 3. Using BSP's we can figure out the order beforehand and create a tree. First we note that any arbitrary point can be on either of it's 2 sides or on the line (which can be regarded as being on either of the sides). When we build our tree, we take a line and put all the lines on one side of it to the left and all the nodes on the other side on the right. So for the above example could wind up with the following tree: 1 / 2 / 3 Alternatively, we could also wind up with this tree: 2 / \ 3 1 Notice that line 2 is the head node, line 3 is on the same side of line 2 as the origin is and line 1 is on the opposite side. Now, I hear you say "but hang on a tic, what if line 3 is the head node? What side of it is line 2 on?". Yes boys and girls, there had to be a catch somewhere and this is it. What you have to do here is split line 2 into *TWO* lines so that each portion falls nice and neatly onto either side of line 3, so you get a tree like this: 3 / \ 2a 2b \ 1 The lines 2a and 2b are portions of the original line 2. If you draw *BOTH* of them on the screen it will look as though you've drawn the entire original line. You don't have to worry about balancing a BSP tree, since you have to traverse every node in it every time you draw the scene anyway. The trick is to figure out how to organise the tree so that you get the *least* number of polygon splits. I tackled this by looking at each polygon yet to be inserted into the tree, calculating how many splits it will cause if it is inserted next and selecting the one which will cause the fewest. This is a very slow way of going about things, O(N^2) I think, but for most games you only need to sort the tree once when you are developping the game and not during the game itself. Extending these concepts to 3D is pretty straight-forward. Let's say that polygon 1 is at the top of the BSP tree and we want to insert polygon 2. If all the points in polygon 2 fall on one side or the other of polygon 1 then you insert it into polygon 2's left or right node. If some points fall on one side and the rest fall on the other, then you have to figure out the line of intersection formed by the planes that each polygon lies in and split polygon 2 along this line. Each of these 2 new polygons will then fall on either side of polygon 1. To draw the objects in a BSP tree you start at the top node and figure out which side of the object your view coordinate is on. You then traverse the node for the *other* side, draw the current object, then traverse the node for the side the view coordinate is on. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Texture Mapping ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Sean Barrett. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ -=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- TEXTURE TEXUE almost everything you need to know to texture map with the PC TXR X -=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- Copyright 1994 Sean Barrett. Not for reproduction (electronic or hardcopy) except for personal use. Contents: 0. warnings 1. terminology and equations 2. the basics Perfect texture mapping DOOM essentials Wraparound textures Non-rectangular polygons 3. the hazards going off the texture map divide by 0 it'll never be fast enough 4. the complexities handling arbitrarily-angled polygons lighting slow 16-bit VGA cards mipmapping -=-=-=-=-=- | Note: I am not providing any references as I simply | derived the math myself and worked out the various | techniques for myself (the 32-bit ADC trick was | pointed out to me in another context by TJC, | author of the Mars demo) over the last two years | (since Wolfenstein 3D and Underworld came out). -=-=-=-=-=- TEXTURE TEXUE TXR 0. Warnings X I assume a RIGHT-handed 3D coordinate system, with X positive to the right, Y positive disappearing into the screen/distance, and Z positive up. To adjust this to the typical left-handed 3D space, simply swap all the 3D Ys & Zs. I assume the screen space is positive-X to the right, positive-Y goes down. Adjust the signs as appropriate for your system. I will present code and pseudo-code in C. I also include some relatively tight inner loops written in assembly, but I'm omitting the details of the loop setup. The inner loops, while usually from real, working code, should generally be taken as examples showing how fast it ought to be possible to run a given task, not as necessarily perfect examples. I often use 32-bit instructions (sorry 286 programmers) because they can double the performance. However, I write in real mode, because 16-bit addressing is often convenient for texture maps, and it's straightforward to use segment registers as pointers to texture maps. The translation to protected mode should not prove problematic, but again, these should more be taken as examples rather than simply being used directly. I optimize for the 486, but I skip some obvious optimizations. For example, I write "loop", because it's simpler and more clear. Production code for the 486 should explicitly decrement and then branch. Similarly, I write "stosb", etc. etc. TEXTURE TEXUE TXR 1. Terminology and Equations X You really probably don't want to read this section first, but rather refer back to it whenever you feel the need. So skip up to section 2 and refer back as appropriate. I could've made this an appendix, but it seems too important to put last. TEX TE Terms T texture: A texture is a pixelmap of colors which is mapped onto a polygon using "texture mapping". The size of the polygon has nothing to do with the size (the number of pixels) in the texture map. run: A run is a row or column of pixels. Normal texture mapping routines process one run in an "inner loop". arbitrarily-angled polygon: a polygon which isn't a floor, wall, or ceiling; technically, a polygon which isn't parallel to the X or Z axes (or X or Y axes in a Z-is-depth coordinate system). texture space: polygon space: polygon coordinate space: Since a texture is flat, or two-dimensional, the relation of the texture to the 3D world can be described with a special coordinate space known by one of these names. Because it is only 2D, the space can be characterized with the location of the texture space in 2D, and two 3D vectors which represent the axes of the coordinate space. Sometimes called "uv" space, because the name of the coordinates are usually u & v. TEX TE Notation T Vectors appear in all caps. Components of vectors are P = < Px, Py, Pz >. Certain variables have consistent usage: x,y,z are coordinates in three-space i,j are screen coordinates u,v are coordinates in texture space a,b,c are "magic coordinates" such that u = a/c, v = b/c TEX TE Equations T Don't let this scare you off! Go read section 2, and come back to this when you're ready. Let P,M, and N be vectors defining the texture space: P is the origin, and M and N are the vectors for the u&v axes. Assume these vectors are in _view space_, where view space is defined as being the space in which the transformation from 3D to 2D is: (x,y,z) -> (i,j) i = x / y j = z / y In other words, you have to adjust P, M, and N to be relative to the view, and if you have multiplications in your perspective computation, you have to multiply the appropriate components of P, M, and N to compute them. Note that since typically in 3D up is positive, and in 2D down is positive, there may be a multiplication by -1 that needs to go into Py, My, Ny. Note that this also assumes that (0,0) in screen space is at the center of the screen. Since it's generally not, simply translate your screen coordinates (i,j) as if they were before applying the texture mapping math (or if you're funky you can modify your viewspace to pre-skew them). For example, if your transforms are: i = Hscale * x / y + Hcenter j = -Vscale * z / y + Vcenter Then you should simply multiply Px, Mx, and Nx by Hscale, and multiply Py, My, and Mz by -Vscale. Then just remember to subtract Hcenter and Vcenter from the i,j values right before plugging them into the texture mapping equations. We begin by computing 9 numbers which are constant for texture mapping the entire polygon (O stands for Origin, H for Horizontal, and V for vertical; why I use these names should become clear eventually): Oa = Nx*Pz - Nz*Px Ha = Nz*Py - Ny*Pz Va = Ny*Px - Nx*Py Ob = Mx*Pz - Mz*Px Hb = Mz*Py - My*Pz Vb = My*Px - Mx*Py Oc = Mz*Nx - Mx*Nz Hc = My*Nz - Mz*Ny Vc = Mx*Ny - My*Nx Ok. Then, for a given screen location (i,j), the formula for the texture space (u,v) coordinates corresponding to it is: a = Oa + i*Ha + j*Va b = Ob + i*Hb + j*Vb c = Oc + i*Hc + j*Hc u = a/c v = b/c TEXTURE TEXUE TXR 2. The Basics X So you've got your polygon 3D engine running, and you'd like to start adding a bit of texture to your flat- or Gouraud-shaded polygons. Well, it will make it look a lot cooler. But let's point out the disadvantages of texture mapping right away: Slower Sometimes hard to see polygon edges Each of these has certain ramifications on the overall approach you want to take with your code, which we'll come back to later, in sections 3 and 4. Practical advice: Don't try to get your riproaringly fast texture mapper running first. Get a very simple, slow, "perfect" texture mapper working first, as described in the first subsection below. This will allow you to make sure you've gotten the equations right. Realize that I can't present the equations appropriate to every scenario, since there are simply too many spaces people can work in. I've chosen to present the math from an extremely simple coordinate space which keeps the texture mapping relatively simple. You'll have to work out the correct transformations to make it work right, and a slow but correct texture mapping routine may help you tweak the code as necessary to achieve this. Use very simple polygons to start your testing; centered and facing the viewer should be your very first one (if done correctly, this will simply scale the texture). TEX TE Perfect Texture Mapping T To start with, we'll do slow but exact "perfect" texture mapping of a square tile with a simple texture map mapped onto it. The polygon is defined in three-space using four points, and the texture map is 256x256 pixels. Note that this is all talking about using floating point, so those of you working in C or Pascal are fine. Those in assembly should realize that you have to do a bit of extra work to use fixed point, or you can beat out the floating point by hand if you want. First we have to "map" the texture onto the polygon. We have to define how the square texture map corresponds to the square polygon. This is relatively simple. Let one corner of the polygon be the origin (location <0,0>) of the texture map. Let each of the other corners correspond to corners just off the edge of the texture map (locations <256, 0>, <256, 256>, and <0, 256>). We'd like to use the equations in section 1, which require vectors P, M, and N, where P is the origin, and M & N are the axes for u&v (which are _roughly_ the coordinates in the texture map, but see below). In other words, P, M and N tells us where the texture lies relative to the polygon. P is the coordinate in three-space where the origin of the texture is. M tells us which way the "horizontal" dimension of the texture lies in three-space, and N the "vertical". Suppose the polygon has four vertices V[0], V[1], V[2], and V[3] (all four of these are vectors). Then, P is simply V[0]. M is a vector going from the origin to the corner <256, 0>, so M is a vector from V[0] to V[1], so M = V[1] - V[0]. N is a vector from the origin to the corner <0, 256>, so N is V[3] - v[0]. P = V[0] M = V[1] - V[0] { note these are vector subtractions } N = V[3] - V[0] Again, remember that we need P, M, and N in the viewspace discussed with the equation, so make sure you've transformed the Vs appropriately, or you can compute P, M, and N in world or object space and transform them into viewspace. Compute the 9 magic numbers (vectors O, H, and V) as described in section 1. Now, take your 3D polygon and process it as normal. Scan convert it so that you have a collection of rows of pixels to process. Now, iterate across each row. For each pixel in the polygon whose screen coordinates are , apply the rest of the math described in section 1; that is, compute a, b, and c, and from them compute . I said before that are basically the texture map coordinates. What they are in truth are the coordinates in texture map space. Because of the way we defined texture map space, we'll actually find that u and v both run from 0..1, not 0..256. This is an advantage for descriptive purposes because u and v are always 0 to 1, regardless of the size of the texture map. So, to convert u&v to pixelmap coordinates, multiply them both by 256. Now, use them as indices into the texture map, output the value found there, and voila, you've texture mapped! The loop should look something like this: [ loop #1 ] for every j which is a row in the polygon screen = 0xA0000000 + 320*j for i = start_x to end_x for this row a = Oa + (Ha * i) + (Va * j) b = Ob + (Hb * i) + (Vb * j) c = Oc + (Hc * i) + (Vc * j) u = 256 * a / c v = 256 * b / c screen[i] = texture_map[v][u] endfor endfor Once you've got that working, congratulations! You're done dealing with the annoying messy part, which is getting those 9 magic numbers computed right. The rest of this is just hard grunt work and trickery trying to make the code faster. From here on in, I'm only going to look at the inner loop, that is, a single run (row or column), and let the rest of the runs be understood. TEX TE Prepare to meet thy DOOM T This subsection is concerned with vastly speeding up texture mapping by restricting the mapper to walls, floors and ceilings, or what is commonly called DOOM-style texture mapping, although it of course predates DOOM (e.g. Ultima Underworld [*], Legends of Valour). [* Yes, Underworld allowed you to tilt the view, but it distorted badly. Underworld essentially used DOOM-style tmapping, and tried to just use that on arbitrarily-angled polygons. I can't even begin to guess what Underworld II was doing for the same thing.] To begin with, let's take loop #1 and get as much stuff out of the inner loop as we can, so we can see what's going on. Note that I'm not going to do low-level optimizations, just mathematical optimizations; I assume you understand that array walks can be turned into pointer walks, etc. [ loop #2 ] a = Oa + Va*j b = Ob + Vb*j c = Oc + Vc*j a += start_x * Ha b += start_x * Hb c += start_x * Hc for i = start_x to end_x u = 256 * a / c v = 256 * b / c screen[i] = texture_map[v][u] a += Ha b += Hb c += Hc endfor With fixed point math, the multiplies by 256 are really just shifts. Furthermore, they can be "premultiplied" into a and b (and Ha and Hb) outside the loop. Ok, so what do we have left? Three integer adds, a texture map lookup, and two extremely expensive fixed-point divides. How can we get rid of the divides? This is the big question in texture mapping, and most answers to it are _approximate_. They give results that are not quite the same as the above loop, but are difficult for the eye to tell the difference. However, before we delve into these, there's a very special case in which we can get rid of the divides. We can move the divide by c out of the loop without changing the results IFF c is constant for the duration of the loop. This is true if Hc is 0. It turns out that Hc is 0 if all the points on the run of pixels are the same depth from the viewer, that is, they lie on a line of so-called "constant Z" (I would call it "constant Y" in my coordinate system). The requirement that a horizontal line of pixels be the same depth turns out to be met by ceilings and floors. For ceilings and floors, Hc is 0, and so the loop can be adjusted to: [ loop #3 ] __setup from loop #2__ u = 256 * a / c v = 256 * b / c du = 256 * Ha / c dv = 256 * Hb / c for i = start_x to end_x screen[i] = texture_map[v][u] u += du v += dv endfor Now _that_ can be a very fast loop, although adjusting the u&v values so they can be used as indices has been glossed over. I'll give some sample assembly in the next section and make it all explicit. First, though, let's look at walls. Walls are almost identical to floors and ceilings. However, with walls, Vc is 0, instead of Hc. This means that to write a loop in which c is constant, we have to walk down columns instead of across rows. This affects scan-conversion, of course. The other thing about walls is that with floors, since you can rotate about the vertical axis (Z axis for me, Y axis for most of you), the horizontal runs on the floors cut across the texture at arbitrary angles. Since you're bound to not tilt your head up or down, and since the polygons themselves aren't tilted, you generally find that for walls, Va is 0 as well. In other words, as you walk down a column of a wall texture, both a & c are constant, so u is constant; you generally only change one coordinate, v, in the texture map. This means the inner loop only needs to update one variable, and can be made to run _very_ fast. The only thing missing from this discussion for creating a DOOM clone is how to do transparent walls, how to do lighting things, and how to make it fast enough. These will be discussed in section 4, although the some of the speed issue is addressed by the inner loops in the next subsection, and the rest of the speed issue is discussed in general terms in section 3. TEX TE ...wrapped around your finger... T So far, we've only looked at texture mapping a single polygon. Of course, it's obvious how to texture map a lot of polygons--just lather, rinse, repeat. But it may seem sort of wasteful to go through all the 3D math and all over and over again if we just want to have one long wall with the same texture repeating over and over again--like linoleum tiles or wallpaper. Well, we don't have to. Let's think about this idea of a "texture map space" some more. We defined it as being a coordinate system "superimposed" on the polygon that told us where the texture goes. However, when we implemented it, we simply used the polygon itself (in essence) as the coordinate space. To see this, make a polygon which is a rectangle, perhaps four times as long as it is tall. When it is drawn, you will see the texture is distorted, stretched out to four times its length in one dimension. Suppose we wanted it to repeat four times instead? The first step is to look at what the definition of the texture map space means. The texture map space shows how the physical pixelmap itself goes onto the polygon. To get a repeating texture map, our first step is to just get one of the copies right. If we set up our P,M, & N so that the M only goes one quarter of the way along the long edge of the rectangle, we'll map the texture onto just that quarter of the rectangle. Here's a picture to explain it: Polygon A-B-C-D Texture map A E u=0 u=1 o--------o-___________ B v=0 11112222 |111112222 ---------o 11112222 |111112222 | 33334444 |111112222 | 33334444 |333334444 | v=1 |333334444 | |333334444 ___________---------o o--------o- C D F So, we used to map (u,v)=(0,0) to A, (u,v)=(1,0) to B, and (u,v) = (0,1) to D. This stretched the texture map out to fill the entire polygon map. Now, instead, we map (u,v)=(1,0) to E. In other words, let P = A, M = E-A, and N = D-A. In this new coordinate space, we will map the texture onto the first quarter of the polygon. What about the rest of the polygon? Well, it simply turns out that for the first quarter 0 <= u <= 1. For the rest, 1 <= u <= 4. To make the texture wrap around, all we have to do is ignore the integer part of u, and look at the fractional part. Thus, as u goes from 1 to 2, we lookup in the texture map using the fractional part of u, or (u-1). This is all very simple, and the upshot is that, once you define P, M, and N correctly, you simply have to mask your fixed-point u&v values; this is why we generally use texture maps whose sides are powers of two, so that we can mask to stay within the texture map. (Also because they fit conveniently into segments this way, and also so that the multiply to convert u&v values from 0..1 to indices is just a shift.) I'm assuming that's a sufficient explanation of the idea for you to get it all setup. So here's the assembly inner loops I promised. I'm not going to bother giving the ultra-fast vertical wall-drawing case, just the horizontal floor/ceiling-drawing case. Note that a mask of 255 (i.e. for a 256x256 texture) can be gotten for free; however, no program that I'm aware of uses texture maps that large, since they simply require too much storage, and they can cache very poorly in the internal cache. First, here's your basic floor/ceiling texture mapper, in C, with wraparound, and explicitly using fixed point math--but no setup. [ loop #4 ] mask = 127, 63, 31, 15, whatever. for (i=0; i < len; ++i) { temp = table[(v >> 16) & mask][(u >> 16) & mask]; u += du; v += dv; } Now, here's an assembly one. This one avoids the shifts and does both masks at the same time, and uses 16 bits of "fractional" precision and however many bits are needed for the coordinates. Note that I assume that the texture, even if it is 64x64, still has each row starting 256 bytes apart. This just requires some creative storage approaches, and is crucial for a fast inner loop, since no shifting&masking is required to assemble the index. mov al,mask mov ah,mask mov mask2,ax ; setup mask to do both at the same time loop5 and bx,mask2 ; mask both coordinates mov al,[bx] ; fetch from the texture map stosb add dx,ha_low ; update fraction part of u adc bl,ha_hi ; update index part of u add si,hb_low ; these are constant for the loop adc bh,hb_hi ; they should be on the stack loop loop5 ; so that ds is free for the texture map This code is decent, but nowhere near as fast as it can be. The main trick to improving performance is to use 32 bit adds instead of two adds. The problem with this is that extra operations are required to setup the indexing into the texture map. Through the use of the ADC trick, these can be minimized. In the following code, bl and bh are unchanged. However, the top half of EBX now contains what used to be in si, and the other values have been moved into registers. ESI contains hb_low in the top half, and ha_hi in the low 8 bits. This means that ADC EBX,ESI achieves the result of two of the additions above. Also, we start using BP, so we move our variables into the data segment and the texture map into FS. loop6 and bx,mask2 mov al,fs:[bx] stosb add dx,bp ; update fractional part of u adc ebx,esi ; update u (BL) and frac. part of v (EBX) adc bh,ch ; update index part of v dec cl jnz loop6 This is a bit faster, although it has one bug. It's possible for the addition into BL to overflow into BH. It might not seem to be, since BL is masked every iteration back down to stay in 0..127, 0..63, or whatever. However, if the step is negative, then BL will be decremented each iteration, and may "underflow" and subtract one from BH. To handle this, you need a seperate version of the loop for those cases. If you're not doing wraparound textures, you can speed the loop up a bit more by removing the and. You can run entirely from registers except for the texture map lookup. Additionally, unrolling the loop once cuts down on loop overhead, and is crucial if you're writing straight to the VGA, since it doubles your throughput to a 16-bit VGA card. Here's a very fast no-wraparound texture mapper. It uses the ADC trick twice. Note that the carry flag is maintained around the loop every iteration; unfortunately the 'and' required for wraparound textures clears the carry flag (uselessly). EBX and EDX contain u & v in their bottom 8 bits, and contain the fractional parts of v & u in their top bits (note they keep the _other_ coordinate's fractional parts). You have to have prepped the carry flag first; if you can't figure this technique out, don't sweat it, or look to see if someone else has a more clear discussion of how to do fast fixed-point walks using 32-bit registers. This loop is longer because it does two pixels at a time. loop7 mov al,[bx] ; get first sample adc edx,esi ; update v-high and u-low adc ebx,ebp ; update u-high and v-low mov bh,dl ; move v-high into tmap lookup register mov ah,[bx] ; get second sample adc edx,esi adc ebx,ebp mov bh,dl mov es:[di],ax ; output both pixels inc di ; add 2 to di without disturbing carry inc di dec cx jnz loop7 I went ahead and 486-optimized the stosw/loop at the end to make cycle-counting easier. All of these instructions are single cycle instructions, except the branch, and the segment-override. So you're looking at roughly 15 cycles for every two pixels. Your caching behavior on the reads and writes will determine the actual speed. It can be unrolled another time to further reduce the loop overhead; the core operations are 9 instructions (10 cycles) for every two pixels. Note the "inc di/inc di", which protects the carry flag. If you unroll it again, four "inc di"s will be required. Unroll it another time, and you're better off saving the carry flag, adding, and restoring, for example "adc ax,ax/add di,8/shr ax,1", rather than 8 "inc di"s. TEX TE Lost My Shape (trying to act casual) T Non-rectangular polygons are trivial under this system. Some approaches require you to specify the (u,v) coordinates for each of the vertices of the polygon. With this technique, you instead specify the 3D coordinates for three of the "vertices" of the texture map. So the easiest way of handling a texture of a complex polygon is simply to use a square texture which is larger than the polygon. For example: P1 P2 x B _______ C x / \ / \ A / \ \ / D \ / \ _______ / x F E P3 Now, we simply define the texture map such that P is P1, M is P2-P1, and N is P3-P1. Then, if our texture looks like this: u=0 u=1 ------------ v=0 |..XXoooooo.. |.XXXXoooooo. |XXXXXXoooooo |.XXXXXXoooo. v=1 |..XXXXXXoo.. Then the regions marked by '.' in the texture map will simply never be displayed anywhere on the polygon. Wraparound textures can still be used as per normal, and concave polygons require no special handling either. Also, you can get special effects by having M and N not be perpendicular to each other. TEXTURE TEXUE TXR 3. The Hazards X This sections discusses some of the pitfalls and things-aren't-quite-as-simple-as-they-sounded issues that come up while texture mapping. All of the information is, to some extent, important, whether you've encountered this problem or not. TEX TE Cl-cl-cl-close to the Edge T At some time when you're texture mapping, you'll discover (perhaps from the screen, perhaps from a debugger) that your U & V values aren't within the 0..1 range; they'll be just outside it. This is one of these "argh" problems. It is possible through very very careful definition of scan-conversion operations to avoid it, but you're likely to encounter it. If you use wraparound textures, you may not ever notice it, however, since when it happens, the texture will simply wraparound and display an appropriate pixel. If not, you may get a black pixel, or just garbage. It'll only happen at the edges of your polygon. The reason this happens is because your scan-conversion algorithm may generate pixels "in the polygon" whose pixel-centers (or corners, depending on how you've defined it) are just outside the texture--that is, they're outside the polygon itself. The right solution to this is to fix your scan-converter. If your texture mapper computes u&v coordinates based on the top-left corner of the pixel (as the one I've defined so far has), make sure your scan-converter only generates pixels whose top-left corner is really within the polygon. If you do this, you may need to make a minor change to my definition of M & N, but I'm not going to discuss this further, since you probably won't do this. A second option is to define P, M, and N such that the texture map space is slightly bigger than the polygon; that is, so that if you go just off the edge of the polygon, you'll still be within the texture map. This is a pain since you end up having to transform extra 3D points to do it. The third, and probably most common solution, is to always use wraparound textures, which hide the problem, but prevent you from using textures that have one edge that highly contrasts with another. The fourth, and probably second most common solution, and the one that turns out to be a real pain, is to "clamp" the u&v values to be within the texture all the time. Naively, you just put this in your inner loop: if (u < 0) u = 0; else if (u > 1) u = 1; if (v < 0) v = 0; else if (v > 1) v = 1; Of course, you don't really do this, since it'd slow you down far too much. You can do this outside the loop, clamping your starting location for each run. However, you can't, under this system, clamp the ending value easily. Remember that in the loop we update u and v with (essentially) Ha/c and Hb/c. These are constant across the entire run, but not constant across the entire polygon, because c has different values for different runs. We can compute du and dv in a different way to allow for clamping. What we do is we explicitly compute (a,b,c) at (start_x, j) as we did before, but we also compute (a,b,c) at (end_x, j). From these we compute (u,v) at start_x & at end_x. Next we clamp both sets of u & v. Then we compute du and dv with du = (u2 - u1) / (end_x - start_x - 1) dv = (v2 - v1) / (end_x - start_x - 1) This is slightly more expensive than the old way, because we have to compute u2 and v2, which requires extra divides. However, for methods that explicitly calculate u&v sets and then compute deltas (and we'll see some in section 4), this is the way to go. One final thing you can do is interpolate the (a,b,c) triple from the vertices as you scan convert. This will guarantee that all (a,b,c) triples computed will lie be within the polygon, and no clamping will be necessary (but deltas must still be computed as above). However, you have to make sure the (a,b,c) values you compute at the vertices are clamped themselves, which is not too hard by a bit more complicated than clamping (u,v) values. TEX TE Out of This Domain -- Zero's Paradox T Divides by zero are ugly. We programmers don't like them. If this were an ideal world (a quick nod to mathematicians and some physicists), the texture mapping equations would be divide-by-zero-free. Unfortunately, it's a repercussion of the exact same problem as above that you can bump into them. Remember above, I noted that it's possible to get (u,v) pairs with a value just outside of the 0..1 range, because a pixel we're texture mapping isn't even in the polygon? Well, even worse, it's possible for this pixel, which isn't in the polygon, to be along the horizon line (vanishing point) for the polygon. If this happens, your Y value (sorry, Z for most of you) would be infinite if you tried to compute the 3D coordinates from the screen coordinates; and in the (u,v) computation, you end up with a 0 value for c. Since u = a/c, blammo, divide by 0. Well, the solution is simple. Test if c is 0, and if it is, don't divide. But what _should_ you do? Well, let's look at an "even worse" case. Suppose the pixel is so far off the polygon it's across the horizon line. In this case, we'll end up with c having the "wrong" sign, and while our divide won't fault on us, our u&v values will be bogus. What do we do then? We can't clamp our a&b&c values very easily. Fortunately, it turns out we don't have to. If this happens, it means the edge of the polygon must be very close to the horizon, or the viewer must be very, very flat to the polygon (if you know what I mean). If so, the viewer can't really tell what should be "right" for the polygon, so if we screw up the u&v values, it really doesn't matter. So the answer is, don't worry if c gets the wrong sign, and if c comes out to be 0, use any value for u&v that you like--(0,0) makes an obvious choice. I've never had a serious problem with this, but it is possible that this could actually give you some pretty ugly results, if, say, two corners of a polygon both "blew up", and you treated them both as being (0,0). It can also cause problems with wraparound polygons not repeating the right amount. TEX TE Do the Dog T Most polygon 3D graphics engines probably use the painter's algorithm for hidden surface removal. You somehow figure out what order to paint the polygons in (depth sort, BSP trees, whatever), and then paint them back-to-front. The nearer polygons obscure the farther ones, and voila!, you're done. This works great, especially in a space combat simulator, where it's rare that you paint lots of pixels. You can texture map this way, too. For example, Wing Commander II doesn't texture map, but it does real time rotation, which involves essentially the same inner loop. Wing Commander II is fast--until a lot of ships are on the screen close to you, at which point it bogs down a bit. If you care about not slowing down too much in the above case, or you want to do an "indoor" renderer with lots of hidden surfaces, you'll find that with texture mapping, you can ill-afford to use the painter's algorithm. You pay a noticeable cost for every pixel you texture map. If you end up hiding 80% of your surfaces (i.e. there are five "layers" everywhere on the screen), you end up "wasting" 80% of the time you spend on texture mapping. To prevent this, you have to use more complex methods of hidden surface removal. These will probably slow you down somewhat, but you should make up for it with the gain in texture mapping. The essential idea is to only texture map each screen pixel once. To do this, you do some sort of "front-to-back" painting, where you draw the nearest surface first. Any pixel touched by this surface should never be considered for drawing again. There are many ways to do this. You can process a single scanline or column at a time and use ray-casting or just "scanline processing", then resolve the overlap between the runs with whatever method is appropriate. You can stay polygonal and maintain "2D clipping" information (a data structure which tracks which pixels have been drawn so far). Beyond getting a fast inner loop for texture mapping, getting a fast hidden-surface-removal technique (and a fast depth-sorting technique if appropriate) is probably the next most crucial thing for your frame rate. But the details are beyond the scope of this article. Note that if you attempt to use a Z-buffer, you will still end up paying all of the costs of texture mapping for every forward-facing polygon (or at least 50% of them if you get really tricky; if you get really, really tricky, the sky's the limit.) I strongly doubt that any PC game now out, or that will come out in the next year, will render full-screen texture mapping through a Z-buffer. (Superimposing a rendered image on a Z-buffered background is a different issue and is no doubt done all the time.) TEXTURE TEXUE TXR 4. The Complexities X In this section we will discuss lots of miscellaneous topics. We'll look at some more optimizations, such as considerations for dealing with slow VGA cards, and how to texture map arbitrarily-angled polygons without doing two divides per pixel. We'll talk about a technique that lets you use textures with high-frequency components, and one way to integrate lighting into texture-mapping. TEX TE Arbitrarily-Angled Polygons T First suggestion: Don't. Set up your world to have all (or mostly) walls and floors. Supporting arbitrarily-angled polygons is going to slow you down, no matter what. The original texture mapping loop, which supported arbitrarily-angled polygons, required two divides per pixel. We don't have to go that slow, but we'll never go as fast as DOOM-style rendering can go. (However, as you start to use more sophisticated lighting algorithms in your inner loop, the cost of handling arbitrarily- angled polygons may start to become less important.) There is one way to texture map such polygons "perfectly" without two divides per pixel, and a host of ways to do it "imperfectly". I'll discuss several of these ways in varying amounts of detail. Your best bet is to implement them all and see which ones you can get to run the fastest but still look good. You might find that one is faster for some cases but not for others. You could actually have an engine which uses all the methods, depending on the polygon it's considering and perhaps a "detail" setting which controls how accurate the approximations are. The "perfect" texture mapping algorithm is described in another article, "Free-direction texture mapping". I'll summarize the basic idea and the main flaw. The basic idea is this. For ceilings and walls, we were able to walk along a line on the screen for which the step in the "c" parameter was 0; this was a line of "constant Z" on the polygon. It turns out that every polygon has lines of "constant Z"--however, they can be at any angle, not necessarily vertical or horizontal. What this means, though, is that if you walk along those lines instead of walking along a horizontal or vertical, you do not need a divide to compute your texture map coordinates, just deltas. The details can be found in the other article. The slope of the line to walk on the screen is something like Hc/Vc. Note, however, that the "DOOM" approach was _just_ an optimization for a special case. The wall & ceiling renderers produce exactly the same results as a perfect texture mapper, for the polygons that they handle (ignoring rounding errors and fixed-point precision effects). This is not true for the "free-direction" texture mapper. While there is a line across the screen for which the polygon has constant Z, you cannot walk exactly along that line, since you must step by pixels. The end result is that while in the texture map space, you move by even steps, in the screen space, you move with ragged jumps. With perfect texture mapping, you always sample from the texture map from the position corresponding to the top-left/center of each pixel. With the free-direction mapper, you sample from a "random" location within the pixel, depending on how you're stepping across the screen. This "random" displacement is extremely systematic, and leads to a systematic distortion of the texture. I find it visually unacceptable with high-contrast textures, compared to perfect texture mapping, but you should try it and decide for yourself. The technically inclined should note that this is simply the normal "floor" renderer with an extra 2D skew, and that while 2D skews are trivial, they are non-exact and suffer from the flaw described above. The only other alternative for arbitrarily-angled polygons is to use some kind of approximation. We can characterize u and v as functions of i (the horizontal screen position; or use 'j' if you wish to draw columns); for instance, u = a / c, where a = q + i*Ha, c = p + i*Hc. So we can say u(i) = (q + i*Ha) / (r + i*Hc). Now, instead of computing u(i) exactly for each i, as we've done until now, we can instead compute some function u'(i) which is approximately equal to u(i) and which can be computed much faster. There are two straightforward functions which we can compute very fast. One is the simple linear function we used for DOOM-style mapping, u'(x) = r + x*s. Since the function we're approximating is curved (a hyperbola), a curved function is another possibility, such as u'(x) = r + x*s + x^2*t. (SGI's Reality Engine apparently uses a cubic polynomial.) If you try both of these approximations on a very large polygon at a sharp angle, you will find that they're not very good, and still cause visible curvature. They are, of course, only approximations. The approximations can be improved with a simple speed/quality trade-off through subdivision. The idea of subdivision is that the approximation is always of high quality for a small enough region, so you can simply subdivide each region until the subregions are small enough to have the desired quality. There are two ways to subdivide. One simple way is to subdivide the entire polygon into smaller polygons. This should be done on the fly, not ahead of time, because only polygons that are at "bad" angles need a lot of subdivision. After dividing a polygon into multiple smaller ones, render each one seperately. Use the original P, M, and N values for all of the new polygons to make the texture remain where it should be after subdivision. The (probably) better way to subdivide is to subdivide runs instead of polygons, and so I'll discuss this in more detail. The essential thing is that to do an approximation, you evaluate the original function at two or more locations and then fit your approximate function to the computed values. One advantage of run subdivision is that you can share points that you evaluated for one subrun with those of the next. First lets turn back to the two approximations under consideration. The first is what is called "bilinear texture mapping", because the function is linear and we're tracking two ("bi") values. To use this method, we compute the function at both endpoints: u1 = u(start_x), u2 = u(end_x). Then we compute our start and step values. To keep things simple, I'm going to assume the approximation function u'(x) is defined from 0..end_x-start_x, not from start_x..end_x. So, the linear function u'(x) = r + s*x, where u'(0) = u1 and u'(end_x - start_x) = u2 is met by letting r = u1, s = (u2 - u1) / (end_x - startx). Now, suppose our run goes from x = 10 to x = 70. If we evaluate u(10), u(20), u(30), u(40),... u(70), then we can have six seperate sections of bilinear texture mapping. For a quadratic, there are several ways to compute it. One way is to compute an additional sample in the middle; u3 = u((start_x + end_x)/2). Then we can fit u1,u2, and u3 to u'(x) = r + s*x + t*x^2 with: len = (end_x - start_x) k = u1 + u2 - u3*2 r = u1 s = (u2 - u1 - 2*k)/len t = 2*k / len^2 Note that to use this in code, you cannot simply use a loop like this: r += s; s += t; because the r,s, and t values aren't correct for discrete advancement. To make them correct, do this during the setup code: R = r S = s + t T = 2*t Then the loop of (...use R..., R += S, S += T) will work correctly. The biquadratic loop will be slower than the linear loop, but will look better with fewer subdivisions. You can share one of the endpoints from one biquadratic section to the next. Note, though, that you require twice as many calculations of u&v values for the same number of subdivisions with a biquadratic vs. a bilinear. Another thing to do is to choose how to subdivide the run more carefully. If you simply divide it in half or into quarters, you'll discover that some of the subruns come out looking better than others. So there are some things you can do to improve the subdivision system. Another thing you can do is to try to make most of your subruns have lengths which are powers of two. This will let you use shifts instead of divides when computing r,s, and t, which cuts down on your overhead, which lets you use more subdivisions and get the same speed. Note something very important. Subdivision increases the overhead per run; biquadratic and other things increase the cost of the inner loop. Before you go crazy trying to optimize your arbitrarily-angled polygon renderer, make sure you're rendering some "typical" scenes. The "right" answer is going to depend on whether you have lots of very shorts runs or fewer, longer runs. If you optimize based on a simple test case, you may end up suboptimal on the final code. You probably still want to have both a column-based and a row-based renderer, and use whichever one the polygon is "closer to" (e.g. if Hc is closer to 0 than Vc, use the row-based). Note that the free-direction renderer looks its worst (to me) for very small rotations, i.e. when Hc or Vc are very close to 0. Since in these cases not much subdivision is needed, even if you choose to use a free-direction mapper as your primary renderer, you might still want to have "almost wall" and "almost floor" renderers as well. Finally, there is one more approximation method you can use, which is faster than any of the ones discussed so far, but is simply totally and utterly wrong. This is the approach used by Michael Abrash in his graphics column in Dr. Dobbs. While it's quite wrong, it works on polygons which are entirely constant Y (sorry, Z), and can be a noticeable speedup. What you do is 2D (instead of 3D) interpolation. You mark each vertex with its coordinates in the texture map. Then when you scan convert, you interpolate these values between vertices on the edges of your runs. Thus, scan conversion will generate runs with (u,v) values for the left and right end. Now simply compute (du,dv) by subtracting and dividing by the length (no clamping will be necessary), and use your fast bilinear inner loop. When combined with 3D polygon subdivision, this approach can actually be useful. A cheat: When the player is moving, set your internal quality settings a little lower. When the player stops, switch back to the normal quality; if the player pauses the game, render one frame in normal quality. If done right, you can get a small boost to your fps without anyone being able to tell that you did it. You may have to use normal quality if the player is only moving very slowly, as well. Note that while this may sound like an utterly cheap trick just to improve the on-paper fps number, it's actually quite related to the "progressive refinement" approach used by some real VR systems (which, when the viewer isn't moving, reuse information from the previous frame to allow them to draw successive frames with more detail). There are a number of ways of improving this cheat intelligently. If the player is moving parallel to a polygon, that polygon will tend to be "stably" texture mapped (similar mapping from frame to frame). If there is any distortion from your approximation, this will be visible to the player. So this means a rule of thumb is to only cheat (draw with above-average distortion) on polygons that are not facing parallel to the direction of motion of the player. TEX TE Light My Fire T If you're texture mapping, it's generally a good idea to light your polygons. If you don't light them, then it may be difficult to see the edge between two walls which have the same texture (for instance, check out the "warehouse" section of registered DOOM, which is sometimes confusing when a near crate looks the same color as a far crate). Lighting is actually pretty straightforward, although you take a speed hit in your inner loop. I'm not going to worry about actual lighting models and such; see other articles for discussion on how to do light-sourced polygons. Instead I'm going to assume you've computed the lighting already. We'll start with "flat-run" shading, wherein an entire run has the same intensity of light falling on it. DOOM uses flat-run shading. A given polygon has a certain amount of light hitting it, which is the same for the entire polygon. In addition, each run of the polygon is sort-of lit by the player. Since runs are always at a constant depth, you can use constant lighting across the run and still change the brightness with distance from the player (DOOM uses something that resembles black fog, technically). So the only real issue is _how_ you actually get the lighting to affect the texture. Several approaches are possible, but the only one that I think anyone actually uses is with a lighting table. The lighting table is a 2D array. You use the light intensity as one index, and the pixelmap color as the other index. You lookup in the table, and this gives you your final output color to display. (With two tables, you can do simple dithering.) So the only thing you have to do is precompute this table. Basically, your inner loop would look something like this: ...compute light... for (i=start_x; i <= end_x; ++i) { color = texture[v >> 16][u >> 16]; output = light_table[light][color]; screen[i] = output; u += du; v += dv; } The next thing to consider is to Gouraud shade your texture map. To do this, you need to compute the light intensity at the left and right edge of the run; look elsewhere for more details on Gouraud shading. Once you've got that, you just do something like this: z = light1 << 16; dz = ((light2 - light1) << 16) / (end_x - start_x); for (i=start_x; i <= end_x; ++i) { color = texture[v >> 16][u >> 16]; output = light_table[z >> 16][color]; screen[i] = color; u += du; v += dv; z += dz; } Note that you shouldn't really do this as I've written the code. light1 and light2 should be calculated with 16 bits of extra precision in the first place, rather than having to be shifted left when computing z. I just did it that way so the code would be self-contained. I'm going to attempt to give a reasonably fast assembly version of this. However, there's a big problem with doing it fast. The 80x86 only has one register that you can address the individual bytes in, and also use for indexing--BX. This means that it's a real pain to make our inner loop alternate texture map lookup and lighting fetch--whereas it's almost trivial on a 680x0. I avoid this somewhat by processing two pixels at a time; first doing two texture map lookups, then doing two lighting lookups. Here's a flat-shading inner loop. I'm doing this code off the top of my head, so it may have bugs, but it's trying to show at least one way you might try to do this. Since I use BP, I put variables in the FS segment, which means DS points to the texture, GS to the lighting table. mov ch,fs:light adc ax,ax loop8 shr ax,1 ; restore carry mov cl,[bx] ; get first sample, setting up cx for color lookup adc edx,esi ; update v-high and u-low adc ebx,ebp ; update u-high and v-low mov bh,dl ; move v-high into tmap lookup register mov ah,[bx] ; get second sample, save it in ah adc edx,esi adc ebx,ebp mov dh,bl ; save value of bl mov bx,cx ; use bx to address color map mov al,gs:[bx] ; lookup color for pixel 1 mov bl,ah ; switch to pixel 2 mov ah,gs:[bx] ; lookup color for pixel 2 mov es:[di],ax ; output both pixels mov bl,dh ; restore bl from dh mov bh,dl adc ax,ax ; save carry so we can do CMP add di,2 cmp di,fs:last_di ; rather than having to decrement cx jne loop8 For a Gouraud shading inner loop, we can now have three different numbers u, v, and z, which we're all adding at every step. To do this, we use THREE adc, and we have to shuffle around which high-bits correspond to which low-bits in a complex way. I'll leave you to figure this out for yourself, but here's an attempt at the inner loop. loop9 shr ax,1 ; restore carry mov al,fs:[bx] ; get first sample mov ah,cl ; save away current z-high into AH ; this makes AX a value we want to lookup adc edx,esi ; update v-high and u-low adc ebx,ebp ; update u-high and z-low adc ecx,z_v_inc ; update z-high and v-low mov bh,dl ; move v-high into tmap lookup register mov ch,fs:[bx] ; get second sample, save it in ch mov dh,bl ; save value of bl mov bx,ax mov al,gs:[bx] ; lookup first color value mov bl,ch mov bh,cl mov ah,gs:[bx] ; lookup second color value mov es:[di],ax ; output both pixels mov bl,dh ; restore bl from dh adc edx,esi adc ebx,ebp adc ecx,z_v_inc mov bh,dl adc ax,ax ; save carry add di,2 cmp di,last_di ; rather than having to decrement cx jne loop9 Notice that both of these loops are significantly slower than the original loop. I'm not personally aware of any generally faster way to do this sort of thing (but the code can be tweaked to be faster). The one exception is that for flat-run shading, you could precompute the entire texture with the right lighting. This would require a lot of storage, of course, but if you view it as a cache, it would let you get some reuse of information from frame to frame, since polygons tend to be lit the same from frame to frame. Finally, here's a brief discussion of transparency. There are two ways to get transparency effects. The first one is slower, but more flexible. You use _another_ lookup table. You have to paint the texture that is transparent after you've drawn things behind it. Then, in the inner loop, you fetch the texture value (and light it) to draw. Then you fetch the pixel that's currently in that location. Lookup in a "transparency" table with those two values as indices, and write out the result. The idea is that you do this: table[new][old]. If new is a normal, opaque, color, then table[new][old] == new, for every value of old. If new is a special "color" which is supposed to be transparent, then table[new][old] == old, for every value of old. This causes old to show through. In addition, you can have translucency effects, where table[new][old] gives a mixture of the colors of old and new. This will let you do effects like the translucent ghosts in the Ultima Underworlds. However, the above approach is extremely slow, since you have to load the value from the pixel map and do the extra table lookup. But it works for arbitrary polygons. DOOM only allows transparency on walls, not on ceilings and floors. Remember we noticed that the special thing about walls is that u is constant as you draw a column from a wall; you are walking down a column in the texture map at the same time you are drawing a column on screen. What this means is that you can use a data structure which encodes where the transparency in each column of the texture map is, and use that _outside_ the inner loop to handle transparency. For example, your data structure tells you that you have a run of 8 opaque pixels, then 3 transparent pixels, then 5 more opaque ones. You scale 8, 3, and 5 by the rate at which you're walking over the textures, and simply treat this as two seperate opaque runs. The details of this method depend on exactly how you're doing your hidden surface removal, and since it doesn't generalize to floors&ceilings, much less to arbitrarily angled polygons, I don't think going into further detail will be very useful (I've never bothered writing such a thing, but I'm pretty sure that's all there is to it). TEX TE The Postman Always Rings Twice T If you're going to write to a slow 16-bit VGA card, you should try your darndest to always write 2 pixels at a time. For texture mapping, your best bet is to build your screen in a buffer in RAM, and then copy it to the VGA all at once. You can do this in Mode 13h or in Mode X or Y, as your heart desires. You should definitely do this if you're painting pixels more than once while drawing. If, however, you wish to get a speedup by not paying for the extra copy, you might like to write directly to the VGA card from your inner loop. You might not think this is very interesting. If the write to the screen buffer in regular RAM is fast, how much can you gain by doing both steps at once, instead of splitting them in two? The reason it is interesting is because the VGA, while slow to accept multiple writes, will let you continue doing processing after a single write. What this means is that if you overlap your texture mapping computation with your write to the VGA, you can as much as double your speed on a slow VGA card. For example, the fastest I can blast my slow VGA card is 45 fps. I can texture map floor-style directly to it at 30 fps. If I texture map to a memory buffer, this is still somewhat slow, more than just the difference between the 30 and 45 fps figures. Thus, my total rate if I write to an offscreen buffer drops as low as 20 fps, depending on exactly what I do in the texture map inner loop. Ok, so, now suppose you've decided it might be a speedup to write directly to the VGA. There are two problems. First of all, if you're in mode X or Y, it's very difficult to write two bytes at a time, which is necessary for this approach to be a win. Second of all, even in mode 13h, it's difficult to write two bytes at a time when you're drawing a column of pixels. I have no answer here. I expect people to stick to offscreen buffers, or to simply process columns at a time and write (at excruciatingly slow rates on some cards) to the VGA only one byte at a time. One option is to set up a page flipping mode 13h (which is possible on some VGA cards), and to paint two independent but adjacent columns at the same time, so that you can write a word at a time. I have a very simple demo that does the latter, but it's not for the faint of heart, and I don't think it's a win once you have a lot of small polygons. Another answer is to have a DOOM-style "low-detail" mode which computes one pixel, duplicates it, and always writes both pixels at the same time. A final answer is just to ignore the market of people with slow VGA cards. I wouldn't be surprised if this approach was commonplace in a year or two. But if you do so with commercial software, please put a notice of this requirement on the box. TEX TE Mipmapping (or is it Mip-Mapping?) T Mipmapping is a very straightforward technique that can be used to significantly improve the quality of your textures, so much so that textures that you could not otherwise use because they look ugly become usable. The problem that mipmapping addresses is as follows. When a texture is far in the distance, such that its on-screen size in pixels is significantly smaller than its actual size as a texture, only a small number of pixels will actually be visible. If the texture contains areas with lots of rapidly varying high contrast data, the texture may look ugly, and, most importantly, moire artifacts will occur. (To see this in DOOM, try shrinking the screen to the smallest setting and going outside in shareware DOOM. Many of the buildings will show moire patterns. In registered DOOM, there is a black-and-blue ceiling pattern which has very bad artifacts if it is brightly lit. Go to the mission with the gigantic round acid pool near the beginning. Cheat to get light amplification goggles (or maybe invulnerability), and you'll see it.) Mipmapping reduces these artifacts by precomputing some "anti-aliased" textures and using them when the textures are in the distance. The basic idea is to substitute a texture map half as big when the polygon is so small that only every other pixel is being drawn anyway. This texture map contains one pixel for every 2x2 square in the original, and is the color average of those pixels. For a 64x64 texture map, you'd have the original map, a 32x32 map, a 16x16 map, an 8x8 map, etc. The mipmaps will smear out colors and lose details. You can best test them by forcing them to be displayed while they're still close to you; once they appear to be working, set them up as described above. Mipmapping causes a somewhat ugly effect when you see the textures switch from one mipmap to the next. However, especially for some textures, it is far less ugly than the effect you would get without them. For example, a fine white-and-black checkerboard pattern (perhaps with some overlaid text) would look very ugly without mipmapping, as you would see random collections of white and black pixels (which isn't too bad), and you would see curving moire patterns (which is). With mipmapping, at a certain distance the whole polygon would turn grey. I do not believe any existing games for the PC use mipmapping. However, examining the data file for the Amiga demo version of Legends of Valour showed smaller copies of textures, which made it look like mipmapping was being used. Mipmapping requires 33% extra storage for the extra texture maps (25% for the first, 25% of 25% for the second, etc.). This may also be a good idea for 2D bitmaps which are scaled (e.g. critters in Underworld & DOOM, or ships in Wing Commander II--although none of those appeared to use it.) SGI's Reality Engine does mipmapping. Actually, it does a texturemap lookup on two of the mipmaps, the "closer" one and the "farther" one, and uses a weighted average between them depending on which size is closer to correct. (The RE also does anti-aliasing, which helps even more.) TEXTURE TEXUE TXR Where Do We Go From Here? X The above discussion mostly covers what is basically the state of the art of texture mapping on the PC. Hopefully in the future every game will be at least as fast as the inner loops in this article allow. As long as people want full-screen images, it'll be a while before we have enough computational power to do more than that. But if we did have more power, what else could we do with it? o Better lighting o Colored lighting (requires complex lookup tables) o Phong shading (interpolation of normals--one sqrt() per pixel!) o Higher resolution (640x400, or 640x400 and anti-alias to 320x200) o A lot more polygons o Bump mapping (can be done today with huge amounts of precomputation) o Curved surfaces ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Free Direction Texture Mapping ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The following article was posted by Hannu Helminen (dm@stekt.oulu.fi) to comp.graphics.algorithms (article 4061). It has been included in the PC-GPE with his permission. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ From X Sat Apr 2 10:24:14 EST 1994 Article: 4061 of comp.graphics.algorithms Newsgroups: comp.graphics.algorithms Path: csc.canberra.edu.au!newshost.anu.edu.au!harbinger.cc.monash.edu.au!msuinfo!agate!howland.reston.ans.net!EU.net!news.funet.fi!ousrvr.oulu.fi!news.oulu.fi!dm From: dm@stekt13.oulu.fi (Hannu Helminen) Subject: Re: extended DOOM: free-direction texture mapping In-Reply-To: dm@stekt13.oulu.fi's message of Fri, 25 Mar 1994 10:37:02 GMT Message-ID: Lines: 160 Sender: news@ousrvr.oulu.fi Organization: University of Oulu, Department of Electrical Engineering, Finland References: Date: Mon, 28 Mar 1994 12:26:24 GMT The idea of free-direction texture-mapping seems to be new to many few people, so I decided to post this short introduction. Warning: The level of this discussion is quite introductory, if you know (or guess) what I'm going to talk about, you probably know as much as I do. First look at the principles. In Doom (and in Wolfenstain) the method used to draw the walls is quite simple. You divide the wall into vertical lines. Then you calculate where the wall should start and where to end on the screen (A and B in my nice ascii-picture), and where in the texture space the corresponding line should start and end. Wall: Texture: \ Y \B \/\^\/\/\ ^\ /\/./\/\/ . \ \/\.\/\/\ . / /\/./\/\/ ./ X /A / Then you simply do a highly optimized loop in which you do all the pixels in the vertical line, pick a color from the texture, put it onto the screen, and move to next position in the texture. The floor is a bit more complicated. (I understand that Wolfenstain had no floor texturing, am I correct?) This time, the floor segment is mapped to a horizontal line, which is simple enough. However, in texture space that same line may be in any direction, so you'll have a 2D line in the texture, like this: Floor: Texture: /\ Y A/...>\B \/\/\.\/\ / \ /\/\.\/\/ \/./\/\/\ /./\/\/\/ X This is old and dull. Now for the new and exciting part: suppose we wish to draw a polygon in 3-space that has free orientation. A bit of thought and a simple extension of the above ideas tell us that we should use a free-direction line in the display coordinates as well. When we map a plane with free orientation to the screen, there is always one direction on the screen, in which the z-coordinate (distance) stays the same. In doom's walls it is vertical, in doom's floors it is horisontal. But there is one such direction for every plane. Why is constant z-coordinate important? These lines have the special property that constant movement along them corresponds to constant movement in texture space. Read the above two paragraphs again until you have understood them, since they are the key thing. The rest is only implementation, following is a short explanation on how I did it. For each polygon you are about to draw on the screen, do the following. Find the plane equation. From that, derive the "constant-z" direction. (Come on, take a piece of paper and a pen, it is quite easy.) It helps to make the distinction between two cases here. Either the "constant-z" direction is more horisontal, or it is more vertical. Suppose that it is more horisontal. The constant-z line equation is now something like y = p*x, where -1 <= p <= 1. ---- --- Example of a constant-z line ---- ---- Now, a change in the coordinate system is in order. x is the same x as before, but y is "slanted" by the factor of p. This means that the x-axis will be "slanted" but y-axis will be the same as before. The next thing is to convert the polygon to this coordinate system. Scan convert it line by line, but along these "slanted" (constant-z) lines. Suppose that we are about to draw a triangle shown below, and the slanted line is the one shown above. So the path to follow on the is as follows (ascii art is back again). The path in the texture is also determined. On the screen: In texture (eg.): \------- Y A... -----/ /./\/\/\/ \ ... / \/./\/\/\ \ ..../ /\/./\/\/ \ /B \/\/./\/\ \ / X \ / X So when you render the triangle, the result would be like this. The numbers are lines of constant Z-value. 22221110 3332221111000 44333222211 544433332 5554444 66555 766 7 Note: you should stack the constant-z lines just as shown in the picture. Implementation notes: this will be a bit slower than DOOM floors, since the algorithm is a bit more complicated. Another thing is that it will not be quite as cache-coherent. If you are rendering big polygons (and have a large cache), it helps to precalculate the pixels lying on the line, so you need not worry about your Bresenham having to choose right pixels. All you need to do is offset the line to right memory offset. The inner loop of this machine could look something like this: zbufpointer = zbufbase + offset; pixelpointer = pixelbase + offset; while (--count >= 0) { off = *precalculatedline++; if (z > zbufpoiner[off]) { zbufpointer[off] = z; pixelpointer[off] = texture(x,y); } x += dx; y += dy; } There is an error of about 0.5 pixel-lengths, since the pixels lying on the constant-z lines are rounded to nearest pixels. Another error can also be seen in the above picture, the line marked with 0's has a small "gap" in it, what should we do with it? Happy programming! --dm -- Hannu dm@stekt.oulu.fi || You have been hacking too long when you Helminen dm@phoenix.oulu.fi || talk of people as users (or end-users) ----------------------------- VLA.NFO ----------------------------------- ÖÄÄÄÄÄÄÄÄÄÄ (% VLA Presents Intro To Starfields %) ÄÄÄÄÄÄÄÄÄÄ· º º ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Written áy : Draeden ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ  VLA Members Are  ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (© Draeden - Main Coder ª) (© Lithium - Coder/Ideas/Ray Tracing ª) (© The Kabal - Coder/Ideas/Artwork ª) (© Desolation - Artwork/Ideas ª) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Finn - Mods/Sounds ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Contact Us On These Boards: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ· º º ³ % Phantasm BBS .................................. (206) 232-5912 ³ ³ * The Deep ...................................... (305) 888-7724 ³ ³ * Dark Tanget Systems ........................... (206) 722-7357 ³ ³ * Metro Holografix .............................. (619) 277-9016 ³ ³ ³ º % - World Head Quarters * - Distribution Site º ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ Or Via Internet Mail For The Group : tkabal@carson.u.washington.edu Or to reach the other members : - draeden@u.washington.edu - - lithium@u.washington.edu - - desolation@u.washington.edu - ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ STARS.TXT ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; TITLE: Star field ;WRITTEN BY: DRAEDEN ; DATE: 03/15/93 ; ; NOTES: ; ;ASSOCIATED FILES: ; ; STARGEN.BAS => Basic program that generates a set of 'randomized' ; numbers. Creates STARRND.DW ; ; STARS.ASM => The asm file. ; ; STARRND.DW => File that contains a set of shuffled numbers order. ; Used to create 'random' star field. ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ A star field is just a series of 3d point plotted onto a 2d plane (your screen). The movement effect is achieved by simply decreasing the Z cordinate and redisplaying the results. The formula for the 3d to 2d conversion is: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ScreenX = ScreenDist * Xpos / Zpos ScreenY = ScreenDist * Ypos / Zpos ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This should make perfect sense. As the object gets futher away, (X,Y) cordinates converge to (0,0). The screen dist is how far away the 'eye' is from the screen, or, as I like to think of it, the window. Naturally, as you get closer to the window, your field of view is greatly enhanced (you can see more). But, because we can't make the monitor bigger, we have to shrink the data that is being displayed. And when we have a large screen distance, we should see less of the virtual world, and the objects should appear bigger. When this formula is translated into assembler, you would immediatly decide that 256 is the best screen distance. Why? Multiplying by 256 on the 386 is as simple as this: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;we want to multiply ax by 256 and put it into dx:ax to set up for division movsx dx,ah ;3 cycles shl ax,8 ;3 cycles -- total 6 ;or we could do it the 'normal way'... mov dx,256 ;2 cycles, but we can have any screen distance imul dx ;9-22 cycles on a 386, 13-26 on a 486 ;a total of 11-28 cycles! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ If you'll take note, the 6 cycle trick is AT LEAST 5 cycles faster than the imul. Anyway... I bet you really don't care about a few cycles at this point, so I won't spend much more time on it... So, as you can see, the math part of it is easy.. the hard part is the what's left. You need a routine that creates a star, presumably random, and another routine that displays all the stars and advances them. Well, that's how I broke it into subroutines... For the routine that creates the star you need it to: 1) See if we already have enough stars going (is NUMSTARS > MAXSTARS ?) 2) If there's room, scan for the first open slot... 3) Now that we've found where to put it, create a star by getting a set of random numbers for the (X,Y) and setting the Z to the maximum. Also select a color for the star. The display routine would need to: 1) Erase the old star. 2) Calculate the screen X & Y positions for the new position. Are they inside the screen boundries? If not, 'kill' the star, otherwise display it. The shade of the color to use must be calculated by using the Z cordinate. Color = BaseColor + Zpos / 256 3) Decrease the Zpos. And the main routine would: 1) Call MakeStars 2) Wait for verticle retrace 3) Call DisplayStars 4) Check for keypress, if there is one, handle it, if its not one we're looking for then exit program. 5) Loop to step 1 To impliment this, we need to create an array of records which has enough room for MAXSTARS. The record would contain the (X,Y,Z) cordinates, the OldDi and the base color for the star. To create a star, it first checks to see if there is room. If there is, then we scan through the array looking%wor an open slot. If we don't find an empty space, then we don't create a star. We create the star by grabbing a pair of (X,Y) cordinates from the list of 'random' numbers and set the Z to MAXZPOS. Then, increase NUMSTARS and return. In displaying the star, we would like to only have to calculate DI once. So we save off a copy of DI in an array after we calculate it for the drawing so that erasing the dot is really quick. Next we calculate the new DI for the dot. This is done by using the formula mentioned above and this one: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ DI = ScreenY * ScreenWidth + ScreenX ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When doing the math, care must be taken to make sure that: a) the Zpos is not zero and X*256/ZPOS is not greater than 32767. will cause a DIVIDE BY ZERO or a DIVIDE OVERFLOW b) SY and SX do not go outside the border of the screen. If either of these conditions are broken, the star must be terminated and calculations for that star must be aborted. Actually, Zpos = 0 is used to signify a nonactive star. To terminate the star, you'd simply change its zpos to 0 and decrease NUMSTARS. To create the different shades, I used: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Color = BaseColor + Zpos/256 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I used 256 as the number to divide by because that enables me to do no dividing at all- I just use AH, because AH = AX / 256 (AH is the upper 8 bits of AX). This relation suggests that the MAXZPOS shoul be 16*256 for 16 shades. So, the MAXZPOS = 4096. The palette will have to be set up so that the shades go from light to black (lower # is lighter). Simple enough. (I hope.) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ RANDOM NUMBERS ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, not truly random numbers, but random enough for a starfield. The problem: There is no way on a PC to create truly random numbers with great speed. Solution: Don't use truly random numbers. Use a chart of non-repeating, shuffled numbers that fall within your desired range. That way the stars will be evenly spread out and the creation of a new star is incredably fast. ( A few MOV instructions) All you have to is grab the number and increase the NEXTRANDOM pointer. I chose to fill in the array half with positive numbers, half with negative with a minimum distance of 10 from 0. I did this so that no stars will 'hit' the screen and just vanish. That doesn't look too good. Here's the BASIC file that made my numbers for me... ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ NumStars = 400 dim RndArray(NumStars) randomize (timer) 'fill the array with numbers from -Numstars/2 to -10 'and from 10 to Numstars/2 i=10 for r = 0 to NumStars/2 RndArray(r)=i i=i+1 next i=-10 for r = NumStars/2 to NumStars RndArray(r)=i i=i-1 next 'randomly shuffle them.. print "Total numbers: ";NumStars print "Shuffling - Please wait... " for q = 1 to numstars/5 for r = 0 to NumStars swnum1 = int(rnd*NumStars+.5) swap RndArray(swnum1),RndArray(r) next next 'write the numbers neatly to a file open "starrnd.dw" for output as 1 cc= 0 ' CC is my "Column Control" print#1, "StarRnd dw ";:print#1, using"####";RndArray(0) for r = 1 to NumStars IF cc=0 THEN ' is this the first one on the line? print#1, "dw ";:print#1, using"####" ;RndArray(r); ELSE print#1, ",";:print#1, using"####"; RndArray(r); END IF cc=cc+1:if cc= 10 then cc=0:print#1," " 'goto the next line next close #1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This brings up another point. Whenever you can write a program in a higher level language to create data for you, do it. It sure beats typing then in by hand. For instance, the palette was made using the REPT macro, the actual data is created by the compiler at compile time. Doing it that way happens to be a whole lot easier than typing in every byte. Last minute note: I rigged the plus and minus keys up so that they control the 'Warpspeed' can be from 0 - MaxWarp, which I set to 90 or something like that. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, that's it for now. See INFO.VLA for information on contacting us. I would like some suggestions on what to write code for. What would you like to see done? What code would you like to get your hands on? Send question, comments, suggestions to draeden@u.washington.edu or post on Phantasm BBS. ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ STARS.ASM ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; TITLE: Star field ;WRITTEN BY: DRAEDEN ; DATE: 03/15/93 ; ; NOTES: Need 386 to execute. ; ;ASSOCIATED FILES: ; ; STARGEN.BAS => Basic program that generates a set of 'randomized' ; numbers. Creates STARRND.DW ; ; STARS.TXT => The text file that explains starfields... ; ; STARRND.DW => File that contains a set of shuffled numbers. ; Used to create 'random' star field. ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ DOSSEG .MODEL SMALL .STACK 200h .CODE .386 ASSUME CS:@CODE, DS:@CODE LOCALS ;=== GLOBALS ;=== Data Includes INCLUDE starrnd.dw ;file that has label StarRnd numbers ;=== DATA Structures Star_Struc STRUC X dw 0 Y dw 0 Z dw 0 OldDi dw 0 ;where to erase last dot Color db 0 ;BASE color. a number 0-16 is added to it Star_Struc ENDS StarStrucSize = 9 ;number of bytes per entry ;=== DATA ScreenWidth EQU 320 ScreenHeight EQU 200 NumRnds EQU 400 ;number of random numbers defined MaxZpos EQU 4096 MinZpos EQU 2 MaxStars EQU 190 NumColors EQU 5 ;number of Base colors in the Color Chart WarpSpeed dw 15 ;how quickly the stars move toward ya MaxWarp EQU 90 Xindex dw 30 ;index into the StarRnd chart for X & Y Yindex dw 230 ; -note they must be different; set em the same to ;see why Cindex dw 0 ;index into ColorChart ColorChart db 0,16,32,48,64,80 ;a list of base colors (-1) Stars Star_Struc MaxStars DUP (<>) ;where all the data is held NumActive dw 0 ;number of stars active Palette db 3 dup (0) ;the palette.. first entrie is BG color (black) i = 15 REPT 16 db 2*i,3*i,4*i i=i-1 ENDM i = 15 REPT 16 db 2*i,2*i,4*i i=i-1 ENDM i = 15 REPT 16 db 3*i,3*i,4*i i=i-1 ENDM i = 15 REPT 16 db 3*i,2*i,4*i i=i-1 ENDM i = 15 REPT 16 db 3*i,3*i,3*i i=i-1 ENDM i = 15 REPT 16 db 2*i,4*i,3*i i=i-1 ENDM ;=== Code Includes ;=== SUBROUTINES ;finds 1st available slot for a star and puts it there MakeStar PROC NEAR pusha mov ax,cs mov es,ax mov ds,ax cmp [NumActive],MaxStars ;is there room for another star? jae NoEmptySpace ;search for 1st available slot mov si,0 TryAgain: cmp word ptr [Stars.Z+si],0 ;is this slot empty? je GotOne ;yes, go fill it add si,StarStrucSize cmp si,MaxStars*StarStrucSize jb TryAgain jmp NoEmptySpace GotOne: ;si points to the record for the star to fill mov di,[Yindex] ;grab index for Ypos add di,di ;multiply by 2 to make it a WORD index mov ax,[StarRnd+di] ;get the number shl ax,3 ;multiply by 8- could been done in BAS file mov [Stars.Y+si],ax ;and save off the number mov di,[Xindex] ;grab index for Xpos add di,di ;... same as above, but for Xpos mov ax,[StarRnd+di] shl ax,3 mov [Stars.X+si],ax mov [Stars.Z+si],MaxZpos ;reset Zpos to the max inc [NumActive] ;we added a star so increase the counter mov di,[Cindex] ;grab the color index mov al,[ColorChart+di] ;grab the BaseColor for the star mov [Stars.Color+si],al ;save it in the record ;increase all the index pointers inc [Cindex] ;increases the color counter cmp [Cindex],NumColors jb OkColor mov [Cindex],0 OkColor: inc [Yindex] ;increases Yindex cmp [Yindex],NumRnds ;note that for this one we jb YindNotZero ; subtract NumRnds from Yindex if we sub [Yindex],NumRnds ; go off the end of the chart YindNotZero: inc [Xindex] ;increase Xindex cmp [Xindex],NumRnds ;have we gone through the entire chart? jb XindNotZero ;nope... ;This clever bit of code makes more use out of the chart by increasing Yindex ; one additional unit each time Xindex goes through the entire chart... the ; result is nearly NumRND^2 random non-repeating points inc [Yindex] ;yes, so change Yindex so that we get a mov ax,[Yindex] ;new set of random (x,y) cmp ax,[Xindex] ;does Xindex = Yindex? jne NotTheSame ;if the index were the same, you'd see ;a graph of the line Y = X, not good... inc [Yindex] ;if they are the same, inc Yindex again NotTheSame: mov [Xindex],0 ;reset Xindex to 0 XindNotZero: ;all done making the star... NoEmptySpace: popa ret MakeStar ENDP DisplayStars PROC NEAR pusha mov ax,cs mov ds,ax mov ax,0a000h mov es,ax mov si,0 DispLoop: mov cx,[Stars.Z+si] or cx,cx ;if Zpos = 0 then this star is dead... je Cont ;continue to the next one- skip this one mov di,[Stars.OldDi+si] ;grab old Di mov byte ptr es:[di],0 ;erase the star cmp cx,MinZpos jl TermStar ;if Zpos < MinZpos then kill the star mov ax,[Stars.Y+si] movsx dx,ah ;'multiply' Ypos by 256 shl ax,8 idiv cx ;and divide by Zpos add ax,ScreenHeight/2 ;center it on the screen mov di,ax cmp di,ScreenHeight ;see if the star is in range. jae PreTermStar ; If not, kill it imul di,ScreenWidth ; DI = Y*ScreenWidth mov ax,[Stars.X+si] movsx dx,ah ;multiply Xpos by 256 shl ax,8 idiv cx ;and divide by Zpos add ax,ScreenWidth/2 ;center it on the screen cmp ax,ScreenWidth ;are we inside the screen boundries? jae PreTermStar add di,ax ; DI = Y * ScreenWidth + X mov [Stars.OldDi+si],di ;save old di ;calculate the color below add ch,cs:[Stars.Color+si] ;i'm dividing cx (the zpos) by 256 and ; putting the result in ch and adding ; the base color to it in one instruction mov es:[di],ch ;put the dot on the screen mov ax,cs:[WarpSpeed] sub cs:[Stars.Z+si],ax ;move the stars inward at WarpSpeed Cont: add si,StarStrucSize ;point to next record cmp si,MaxStars*StarStrucSize ;are we done yet? jb DispLoop popa ret PreTermStar: mov [Stars.Z+si],1 ;this is here so that the star will get erased jmp short Cont ;next time through if I just went off and killed ;the star, it would leave a dot on the screen TermStar: mov [Stars.Z+si],0 ;this actually kills the star, after it has dec [NumActive] ;been erased jmp short Cont DisplayStars ENDP ;=== CODE START: mov ax,cs mov ds,ax mov es,ax mov ax,0013h ;set vid mode 320x200x256 graph int 10h mov dx,offset Palette mov ax,1012h ; WRITE palette mov bx,0 mov cx,256 ;write entire palette int 10h ;doesn't matter if we didnt define it all StarLoop: call MakeStar ;make stars 2x as thick call MakeStar mov dx,3dah VRT: in al,dx test al,8 jnz VRT ;wait until Verticle Retrace starts NoVRT: in al,dx test al,8 jz NoVRT ;wait until Verticle Retrace Ends call DisplayStars mov ah,1 ;check to see if a char is ready int 16h jz StarLoop ;nope, continue mov ah,0 int 16h ;get the character & put in AX cmp al,"+" ;compare ASCII part (al) to see what was pressed jne NotPlus inc [WarpSpeed] cmp [WarpSpeed],MaxWarp jbe StarLoop mov [WarpSpeed],MaxWarp jmp StarLoop NotPlus: cmp al,"-" jne NotMinus dec [WarpSpeed] cmp [WarpSpeed],0 jge StarLoop mov [WarpSpeed],0 Jmp StarLoop NotMinus: mov ax,0003h ;set 80x25x16 char mode int 10h mov ax,4c00h ;return control to DOS int 21h END START ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ STARRND.DW ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ StarRnd dw 166 dw 67, 102, 46,-173,-154,-210,-192, 173,-196, -81 dw -50, 36, 50,-200, -95, 209, -16,-179, -30, 18 dw 174, 197, 127, 71, 29,-121,-160,-176, 19, -52 dw -185, 89, 172, 74,-156, 157,-125, 144, -34, 69 dw 17, -40, 64, -98,-153, 125, 160, 140,-204, 141 dw 137,-165, -14, 154,-146, 119, 123, 165,-130, 168 dw -180, 143, 52, 107,-107,-102, 57, 27, 117, 37 dw 126, 15, -89, 184, 116, 183, -99,-139, 150, 188 dw 38, 90, 93,-194, 207,-187, 62, 59, 196, 12 dw -174, 54, 146,-137, 198, 162, 155,-163, -77,-144 dw 191,-132, -43, 151,-103, 20, -46, 13,-140, 31 dw 130,-169,-188, 109, -33,-150,-170, 68, -75,-201 dw -100,-171, -19, -61,-206, 149, 99, -76,-186, -44 dw -178, 34, 61, 28, 114, 199, 201, -83, -27, 63 dw -38, 204, 208,-112,-208, 122, -90, 23,-122, 161 dw 35,-168, 170,-164,-151, 75, -60,-109, 85, 193 dw 45,-175,-134, 205, -21, 49, 133, -85, -47, -37 dw -29, -96, -66, 73,-118, 147, -53, 120, 153,-155 dw -11, 11, 95, -26, 134,-145, -49, -74, 42,-124 dw 189, -42, 92,-167, 88,-126,-129,-108,-193, 195 dw 190,-106,-117, 203, 84, 139,-123, -94, -88,-158 dw 181, -97, -20, 82, -57, 112, -35, 14, -56, -58 dw 200, 80,-183, 106, 87, 30, 51, -28, 98, -12 dw -191,-128, -13,-184, 136, 43,-166, -62, -73,-116 dw -31,-135,-101, 25, 41, -82, 110, 10, -45, -41 dw 97, 175, 138, 171, 72,-133,-157, 58,-104, 187 dw 192, -68, -87, 169,-110, 91, 129, 104, -70,-114 dw -138,-115,-141, -67,-195, -79, -69, 40,-147, -80 dw -119, 128, 152,-209, 83, 53, 159, 66,-190, 81 dw -92, -10,-181, 135, 60, 33, -25, 70, 22, -72 dw 103, -23, 131, 79, -64, 55, -86, -32,-182,-136 dw 26, -54,-172,-148, 148, -65,-152,-207, -39, -71 dw 65, 179,-177, 24, 118, -59, -63, 44, 105, 206 dw 178, -84,-202, 132, 186, -17, 76, 176, -22, 177 dw -198,-159,-162, 78, 77, -55,-120,-203,-113, 156 dw -189,-197, 124, 121,-142, -15,-205, 56, 158, -18 dw -93,-161, 39, 48, 101, -91, 182,-127, 108, 111 dw -36,-143, 21,-149, -78, -48, 164, 202, 185, 180 dw -51,-199, 100, 194, 32, -24, 142, 86,-111, 47 dw 115,-105, 16, 167, 94, 163, 96, 113,-131, 145 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ STARGEN.BAS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ' 'Written by: Draeden /VLA ' Date: 03/15/93 ' ' Notes: Used for generating 'random' data for Stars.asm ' NumStars = 400 dim RndArray(NumStars) randomize (timer) 'fill the array with numbers from -Numstars/2 to -10 'and from 10 to Numstars/2 i=10 for r = 0 to NumStars/2 RndArray(r)=i i=i+1 next i=-10 for r = NumStars/2 to NumStars RndArray(r)=i i=i-1 next 'randomly shuffle them.. print "Total numbers: ";NumStars print "Shuffling - Please wait... " for q = 1 to numstars/5 for r = 0 to NumStars swnum1 = int(rnd*NumStars+.5) swap RndArray(swnum1),RndArray(r) next next 'write the numbers neatly to a file open "starrnd.dw" for output as 1 cc= 0 print#1, "StarRnd dw ";:print#1, using"####";RndArray(0) for r = 1 to NumStars IF cc=0 THEN print#1, "dw ";:print#1, using"####" ;RndArray(r); ELSE print#1, ",";:print#1, using"####"; RndArray(r); END IF cc=cc+1:if cc= 10 then cc=0:print#1," " next close #1 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º How to code youre own "Fire" Routines º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Hiya Puppies! Hopefully this information file will give you all the information you need to code youre own fire routines, seen in many demo's and also to actually take it all further and develop youre own effects.. Ok, so lets get on.... Setting up first thing we need to do is set up two arrays, the size of the arrays depends on the many things, screen mode, speed of computer etc, its not really important, just that they should both be the same size... I'll use 320x200 (64000 byte) arrays for this text, because that happens to be the size needed for using a whole screen in vga mode 13h. The next thing we need to do is set a gradient palette, this can be smoothly gradiated through ANY colours, but for the purpose of this text lets assume the maximum value is a white/yellow and the bottom value is black, and it grades through red in the middle. Ok, we have two arrays, lets call them startbuffer and screenbuffer, so we know whats going on. Firstly, we need to setup an initial value for the start buffer... so what we need is a random function, that returns a value between 0 and 199 (because our screen is 200 bytes wide) this will give us the initial values for our random "hotspots" so we do this as many times as we think is needed, and set all our bottom line values of the start buffer to the maximum colour value. (so we have the last 300 bytes of the start buffer set randomly with our maximum colour, usually if we use a full palette this would be 255 but it can be anything that is within our palette range.) Ok, thats set the bottom line up.. so now we need to add the effect, for this we need to copy the start buffer, modify it and save it to the screenbuffer, we do this by averaging the pixels (this is in effect what each byte in our array represents) surrounding our target.... It helps to think of these operations in X,Y co-ordinates.... Lets try a little diagram for a single pixel..... This is the startbuffer This is our screenbuffer ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ÚÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄÂÄÄÄ¿ ³0,0³0,1³0,2³0,3³0,4³ etc... ³ ³ ³ ³ ³ ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³1,0³1,1³1,2³1,3³1,4³ etc.. ³ ³X,Y³ ³ ³ ³ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ÃÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄÅÄÄÄ´ ³2,0³2,1³2,2³2,3³2,4³ etc.. ³ ³ ³ ³ ³ ³ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ ÀÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÁÄÄÄÙ Here we're going to calulate the value for X,Y (notice I didnt start at 0,0 for calculating our new pixel values?? thats because we need to average the 8 surrounding pixels to get out new value.. and the pixels around the edges wouldn't have 8 pixels surrounding them), so what we need to do to get the value for X,Y is to average the values for all the surrounding pixels... that means adding 0,0 0,1 0,2 + 1,0 1,2 + 2,0 2,1 2,2 and then dividing the total by 8 (the number of pixels we've takes our averages from), but there's two problems still facing us.. 1) The fire stays on the bottom line.... 2) Its slow.... 3) The fire colours dont fade... Ok, so first thing, we need to get the fire moving! :) this is really VERY easy. All we need to do is to take our average values from the pixel value BELOW the pixel we are calculating for, this in effect, moves the lines of the new array up one pixel... so for example our old X,Y value we were calculating for was 1,1 so now we just calculate for 2,1 and put the calculated value in the pixel at 1,1 instead.. easy.. The second problem can be approached in a few ways.. first and easiest is to actually calculate less pixels in our averaging.. so instead of the 8 surrounding pixels we calculate for example, 2 pixels, the one above and the one below our target pixel (and divide by 2 instead of 8) this saves a lot of time, another approach is to use a screen mode, where you can set 4 pixels at a time, or set up the screen so that you can use smaller arrays (jare's original used something like 80X50 mode) which in effect reduces to 1/4 the number of pixels needed to be calculated. The third problem is just a matter of decrementing the calculated value that we get after averaging by 1 (or whatever) and storing that value. Last but not least, we need to think about what else can be done... well, you can try setting a different palette, you can also try setting the pixel value we calculated from to another place, so say, instead of calculating from one pixel below our target pixel, you use one pixel below and 3 to the right of our target... FUN! :)) Well, I hope I didnt confuse you all too much, if you need anything clearing up about this, then email me at pc@espr.demon.co.uk ok? Written by Phil Carlisle (aka Zoombapup // CodeX) 1994. ZSoft PCX File Format Technical Reference Manual Introduction 2 Image File (.PCX) Format 3 ZSoft .PCX FILE HEADER FORMAT 4 Decoding .PCX Files 6 Palette Information Description 7 EGA/VGA 16 Color Palette Information 7 VGA 256 Color Palette Information 7 24-Bit .PCX Files 8 CGA Color Palette Information 8 CGA Color Map 8 PC Paintbrush Bitmap Character Format 9 Sample "C" Routines 10 FRIEZE Technical Information 14 General FRIEZE Information 14 7.00 and Later FRIEZE 14 FRIEZE Function Calls 15 FRIEZE Error Codes 18 Introduction This booklet was designed to aid developers and users in understanding the technical aspects of the .PCX file format and the use of FRIEZE. Any comments, questions or suggestions about this booklet should be sent to: ZSoft Corporation Technical Services ATTN: Code Librarian 450 Franklin Rd. Suite 100 Marietta, GA 30067 Technical Reference Manual information compiled by: Dean Ansley Revision 5 To down load additional information and the source for a complete Turbo Pascal program to show .PCX files on a CGA/EGA/VGA graphics display, call our BBS at (404)427-1045. You may use a 9600 baud modem or a 2400 baud standard modem. Your modem should be set for 8 data bits, 1 stop bit, and NO parity. Image File (.PCX) Format If you have technical questions on the format, please do not call technical support. ZSoft provides this document as a courtesy to its users and developers. It is not the function of Technical Support to provide programming assistance. If something is not clear, leave a message on our BBS, Compuserve, or write us a letter at the above address. The information in this section will be useful if you want to write a program to read or write PCX files (images). If you want to write a special case program for one particular image format you should be able to produce something that runs twice as fast as "Load from..." in PC Paintbrush. Image files used by PC Paintbrush product family and FRIEZE (those with a .PCX extension) begin with a 128 byte header. Usually you can ignore this header, since your images will probably all have the same resolution. If you want to process different resolutions or colors, you will need to interpret the header correctly. The remainder of the image file consists of encoded graphic data. The encoding method is a simple byte oriented run-length technique. We reserve the right to change this method to improve space efficiency. When more than one color plane is stored in the file, each line of the image is stored by color plane (generally ordered red, green, blue, intensity), As shown below. Scan line 0: RRR... (Plane 0) GGG... (Plane 1) BBB... (Plane 2) III... (Plane 3) Scan line 1: RRR... GGG... BBB... III... (etc.) The encoding method is: FOR each byte, X, read from the file IF the top two bits of X are 1's then count = 6 lowest bits of X data = next byte following X ELSE count = 1 data = X Since the overhead this technique requires is, on average, 25% of the non-repeating data and is at least offset whenever bytes are repeated, the file storage savings are usually considerable. ZSoft .PCX FILE HEADER FORMAT Byte Item Size Description/Comments 0 Manufacturer 1 Constant Flag, 10 = ZSoft .pcx 1 Version 1 Version information 0 = Version 2.5 of PC Paintbrush 2 = Version 2.8 w/palette information 3 = Version 2.8 w/o palette information 4 = PC Paintbrush for Windows(Plus for Windows uses Ver 5) 5 = Version 3.0 and > of PC Paintbrush and PC Paintbrush +, includes Publisher's Paintbrush . Includes 24-bit .PCX files 2 Encoding 1 1 = .PCX run length encoding 3 BitsPerPixel 1 Number of bits to represent a pixel (per Plane) - 1, 2, 4, or 8 4 Window 8 Image Dimensions: Xmin,Ymin,Xmax,Ymax 12 HDpi 2 Horizontal Resolution of image in DPI* 14 VDpi 2 Vertical Resolution of image in DPI* 16 Colormap 48 Color palette setting, see text 64 Reserved 1 Should be set to 0. 65 NPlanes 1 Number of color planes 66 BytesPerLine 2 Number of bytes to allocate for a scanline plane. MUST be an EVEN number. Do NOT calculate from Xmax-Xmin. 68 PaletteInfo 2 How to interpret palette- 1 = Color/BW, 2 = Grayscale (ignored in PB IV/ IV +) 70 HscreenSize 2 Horizontal screen size in pixels. New field found only in PB IV/IV Plus 72 VscreenSize 2 Vertical screen size in pixels. New field found only in PB IV/IV Plus 74 Filler 54 Blank to fill out 128 byte header. Set all bytes to 0 NOTES: All sizes are measured in BYTES. All variables of SIZE 2 are integers. *HDpi and VDpi represent the Horizontal and Vertical resolutions which the image was created (either printer or scanner); i.e. an image which was scanned might have 300 and 300 in each of these fields. Decoding .PCX Files First, find the pixel dimensions of the image by calculating [XSIZE = Xmax - Xmin + 1] and [YSIZE = Ymax - Ymin + 1]. Then calculate how many bytes are required to hold one complete uncompressed scan line: TotalBytes = NPlanes * BytesPerLine Note that since there are always an even number of bytes per scan line, there will probably be unused data at the end of each scan line. TotalBytes shows how much storage must be available to decode each scan line, including any blank area on the right side of the image. You can now begin decoding the first scan line - read the first byte of data from the file. If the top two bits are set, the remaining six bits in the byte show how many times to duplicate the next byte in the file. If the top two bits are not set, the first byte is the data itself, with a count of one. Continue decoding the rest of the line. Keep a running subtotal of how many bytes are moved and duplicated into the output buffer. When the subtotal equals TotalBytes, the scan line is complete. There should always be a decoding break at the end of each scan line. But there will not be a decoding break at the end of each plane within each scan line. When the scan line is completed, there may be extra blank data at the end of each plane within the scan line. Use the XSIZE and YSIZE values to find where the valid image data is. If the data is multi-plane, BytesPerLine shows where each plane ends within the scan line. Continue decoding the remainder of the scan lines (do not just read to end-of-file). There may be additional data after the end of the image (palette, etc.) Palette Information Description EGA/VGA 16 Color Palette Information In standard RGB format (IBM EGA, IBM VGA) the data is stored as 16 triples. Each triple is a 3 byte quantity of Red, Green, Blue values. The values can range from 0-255, so some interpretation may be necessary. On an IBM EGA, for example, there are 4 possible levels of RGB for each color. Since 256/4 = 64, the following is a list of the settings and levels: Setting Level 0-63 0 64-127 1 128-192 2 193-254 3 VGA 256 Color Palette Information ZSoft has recently added the capability to store palettes containing more than 16 colors in the .PCX image file. The 256 color palette is formatted and treated the same as the 16 color palette, except that it is substantially longer. The palette (number of colors x 3 bytes in length) is appended to the end of the .PCX file, and is preceded by a 12 decimal. Since the VGA device expects a palette value to be 0-63 instead of 0-255, you need to divide the values read in the palette by 4. To access a 256 color palette: First, check the version number in the header; if it contains a 5 there is a palette. Second, read to the end of the file and count back 769 bytes. The value you find should be a 12 decimal, showing the presence of a 256 color palette. 24-Bit .PCX Files 24 bit images are stored as version 5 or above as 8 bit, 3 plane images. 24 bit images do not contain a palette. Bit planes are ordered as lines of red, green, blue in that order. CGA Color Palette Information NOTE: This is no longer supported for PC Paintbrush IV/IV Plus. For a standard IBM CGA board, the palette settings are a bit more complex. Only the first byte of the triple is used. The first triple has a valid first byte which represents the background color. To find the background, take the (unsigned) byte value and divide by 16. This will give a result between 0-15, hence the background color. The second triple has a valid first byte, which represents the foreground palette. PC Paintbrush supports 8 possible CGA palettes, so when the foreground setting is encoded between 0 and 255, there are 8 ranges of numbers and the divisor is 32. CGA Color Map Header Byte #16 Background color is determined in the upper four bits. Header Byte #19 Only upper 3 bits are used, lower 5 bits are ignored. The first three bits that are used are ordered C, P, I. These bits are interpreted as follows: c: color burst enable - 0 = color; 1 = monochrome p: palette - 0 = yellow; 1 = white i: intensity - 0 = dim; 1 = bright PC Paintbrush Bitmap Character Format NOTE: This format is for PC Paintbrush (up to Vers 3.7) and PC Paintbrush Plus (up to Vers 1.65) The bitmap character fonts are stored in a particularly simple format. The format of these characters is as follows: Header font width byte 0xA0 + character width (in pixels) font height byte character height (in pixels) Character Width Table char widths (256 bytes) each char's width + 1 pixel of kerning Character Images (remainder of the file) starts at char 0 (Null) The characters are stored in ASCII order and as many as 256 may be provided. Each character is left justified in the character block, all characters take up the same number of bytes. Bytes are organized as N strings, where each string is one scan line of the character. For example, each character in a 5x7 font requires 7 bytes. A 9x14 font uses 28 bytes per character (stored two bytes per scan line in 14 sets of 2 byte packets). Custom fonts may be any size up to the current maximum of 10K bytes allowed for a font file. There is a maximum of 4 bytes per scan line. Sample "C" Routines The following is a simple set of C subroutines to read data from a .PCX file. /* This procedure reads one encoded block from the image file and stores a count and data byte. Return result: 0 = valid data stored, EOF = out of data in file */ encget(pbyt, pcnt, fid) int *pbyt; /* where to place data */ int *pcnt; /* where to place count */ FILE *fid; /* image file handle */ { int i; *pcnt = 1; /* assume a "run" length of one */ if (EOF == (i = getc(fid))) return (EOF); if (0xC0 == (0xC0 & i)) { *pcnt = 0x3F & i; if (EOF == (i = getc(fid))) return (EOF); } *pbyt = i; return (0); } /* Here's a program fragment using encget. This reads an entire file and stores it in a (large) buffer, pointed to by the variable "bufr". "fp" is the file pointer for the image */ int i; long l, lsize; lsize = (long )hdr.BytesPerLine * hdr.Nplanes * (1 + hdr.Ymax - hdr.Ymin); for (l = 0; l < lsize; ) /* increment by cnt below */ { if (EOF == encget(&chr, &cnt, fp)) break; for (i = 0; i < cnt; i++) *bufr++ = chr; l += cnt; } The following is a set of C subroutines to write data to a .PCX file. /* Subroutine for writing an encoded byte pair (or single byte if it doesn't encode) to a file. It returns the count of bytes written, 0 if error */ encput(byt, cnt, fid) unsigned char byt, cnt; FILE *fid; { if (cnt) { if ((cnt == 1) && (0xC0 != (0xC0 & byt))) { if (EOF == putc((int )byt, fid)) return(0); /* disk write error (probably full) */ return(1); } else { if (EOF == putc((int )0xC0 | cnt, fid)) return (0); /* disk write error */ if (EOF == putc((int )byt, fid)) return (0); /* disk write error */ return (2); } } return (0); } /* This subroutine encodes one scanline and writes it to a file. It returns number of bytes written into outBuff, 0 if failed. */ encLine(inBuff, inLen, fp) unsigned char *inBuff; /* pointer to scanline data */ int inLen; /* length of raw scanline in bytes */ FILE *fp; /* file to be written to */ { unsigned char this, last; int srcIndex, i; register int total; register unsigned char runCount; /* max single runlength is 63 */ total = 0; runCount = 1; last = *(inBuff); /* Find the pixel dimensions of the image by calculating [XSIZE = Xmax - Xmin + 1] and [YSIZE = Ymax - Ymin + 1]. Then calculate how many bytes are in a "run" */ for (srcIndex = 1; srcIndex < inLen; srcIndex++) { this = *(++inBuff); if (this == last) /* There is a "run" in the data, encode it */ { runCount++; if (runCount == 63) { if (! (i = encput(last, runCount, fp))) return (0); total += i; runCount = 0; } } else /* No "run" - this != last */ { if (runCount) { if (! (i = encput(last, runCount, fp))) return(0); total += i; } last = this; runCount = 1; } } /* endloop */ if (runCount) /* finish up */ { if (! (i = encput(last, runCount, fp))) return (0); return (total + i); } return (total); } FRIEZE Technical Information General FRIEZE Information FRIEZE is a memory-resident utility that allows you to capture and save graphic images from other programs. You can then bring these images into PC Paintbrush for editing and enhancement. FRIEZE 7.10 and later can be removed from memory (this can return you up to 90K of DOS RAM, depending on your configuration). To remove FRIEZE from memory, change directories to your paintbrush directory and type the word "FRIEZE". 7.00 and Later FRIEZE The FRIEZE command line format is: FRIEZE {PD} {Xn[aarr]} {flags} {video} {hres} {vres} {vnum} Where: {PD} Printer driver filename (without the .PDV extension) {Xn[aarr]} X=S for Serial Printer, P for Parallel Printer, D for disk file. (file is always named FRIEZE.PRN) n = port number aa = Two digit hex code for which return bits cause an abort (optional) rr = Two digit hex code for which return bits cause a retry (optional) NOTE: These codes represent return values from serial or parallel port BIOS calls. For values see and IBM BIOS reference (such as Ray Duncan's Advanced MS-DOS Programming). {flags}Four digit hex code First Digit controls Length Flag Second Digit controls Width Flag Third Digit controls Mode Flag Fourth Digit controls BIOS Flag 0 - None 1 - Dual Monitor Present 2 - Use internal (true) B/W palette for dithering 2 color images 4 - Capture palette along with screen IN VGA ONLY Frieze 8.08 & up ONLY) NOTE: The length, width and mode flags are printer driver specific. See PRINTERS.DAT on disk 1 (or Setup Disk) for correct use. In general width flag of 1 means wide carriage, and 0 means standard width. Length flag of 0 and mode flag of 0 means use default printer driver settings. If you need to use more than one BIOS flag option, add the needed flag values and use the sum as the flag value. {video} Video driver combination, where the leading digit signifies the high level video driver and the rest signifies the low level video driver Example = 1EGA - uses DRIVE1 and EGA.DEV {hres} Horizontal resolution of the desired graphics mode {vres} Vertical resolution of the desired graphics mode {vnum} Hardware specific parameter (usually number of color planes) Note: The last four parameters can be obtained from the CARDS.DAT file, in your PC Paintbrush product directory. FRIEZE Function Calls FRIEZE is operated using software interrupt number 10h (the video interrupt call). To make a FRIEZE function call, load 75 (decimal) into the AH register and the function number into the CL register, then either load AL with the function argument or load ES and BX with a segment and offset which point to the function argument. Do an int 10h. FRIEZE will return a result code number in AX. All other registers are preserved. In general, a result code of 0 means success and other values indicate errors. However, function 20 (get Frieze Version) behaves differently; see below. No. Definition Arguments 0 Reserved 1 Load Window ES:BX - string (filename to read from) 2 Save Window ES:BX - string (filename to write to) 3 Reserved 4 Reserved 6 Reserved 7 Set Window Size ES:BX - 4 element word vector of window settings: Xmin, Ymin, Xmax, Ymax 8 Reserved 9 Set Patterns ES:BX - 16 element vector of byte values containing the screen-to-printer color correspondence 10 Get Patterns ES:BX - room for 16 bytes as above 11 Set Mode 12,13,14 Reserved 15 Get Window ES:BX - room for 4 words of the current window settings 16 Set Print Options ES:BX - character string of printer options. Same format as for the FRIEZE command. 17, 18, 19 Reserved 20 Get FRIEZE Version. AH gets the whole number portion and AL gets the decimal portion of the version number. (eg. for Freize vesion 7.41, AH will contain 7 and AL will contain 41. If AH =0, you are calling a pre-7.0 version of FRIEZE). 21 Set Parameters ES:BX points to an 8 word table (16 bytes) of parameter settings: TopMargin, LeftMargin, HSize,VSize, Quality/Draft Mode, PrintHres, PrintVres, Reserved. Margins and sizes are specified in hundredths of inches. Q/D mode parameter values: 0 - draft print mode 1 - quality print mode Print resolutions are specified in DPI. Any parameter which should be left unchanged may be filled with a (-1) (0FFFF hex). The reserved settings should be filled with a (-1). 22 Get Parameters ES:BX points to an 8 word table (16 bytes) where parameter settings are held. 23 Get Printer Res ES:BX points to a 12 word table (24 bytes) that holds six printer resolution pairs. 24 Reserved (versions 8.00 & up) FRIEZE Error Codes When FRIEZE is called using interrupt 10 hex, it will return an error code in the AX register. A value of zero shows that there was no error. A nonzero result means there was an error. These error codes are explained below. 0 No Error 1 Printout was stopped by user with the ESC key 2 Reserved 3 File read error 4 File write error 5 File not found 6 Invalid Header - not an image, wrong screen mode 7 File close error 8 Disk error - usually drive door open 9 Printer error - printer is off or out of paper 10 Invalid command - CL was set to call a nonexistent FRIEZE function 11 Can't create file - write protect tab or disk is full 12 Wrong video mode - FRIEZE cannot capture text screens. Technical Reference Manual Including information for: Publisher's Paintbrushr PC Paintbrush IVTM PC Paintbrush IV PlusTM PC Paintbrush PlusTM PC Paintbrushr FRIEZETM Graphics PaintbrushTM Revision 5 ZSoft Corporation 450 Franklin Rd. Suite 100 Marietta, GA 30067 (404) 428-0008 (404) 427-1150 Fax (404) 427-1045 BBS Copyright c 1985, 1987, 1988, 1990, 1991, ZSoft Corporation All Rights Reserved Graphics File Formats This topic describes the graphics-file formats used by the Microsoft Windows operating system. Graphics files include bitmap files, icon-resource files, and cursor-resource files. Bitmap-File Formats Windows bitmap files are stored in a device-independent bitmap (DIB) format that allows Windows to display the bitmap on any type of display device. The term "device independent" means that the bitmap specifies pixel color in a form independent of the method used by a display to represent color. The default filename extension of a Windows DIB file is .BMP. Bitmap-File Structures Each bitmap file contains a bitmap-file header, a bitmap-information header, a color table, and an array of bytes that defines the bitmap bits. The file has the following form: BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; RGBQUAD aColors[]; BYTE aBitmapBits[]; The bitmap-file header contains information about the type, size, and layout of a device-independent bitmap file. The header is defined as a BITMAPFILEHEADER structure. The bitmap-information header, defined as a BITMAPINFOHEADER structure, specifies the dimensions, compression type, and color format for the bitmap. The color table, defined as an array of RGBQUAD structures, contains as many elements as there are colors in the bitmap. The color table is not present for bitmaps with 24 color bits because each pixel is represented by 24-bit red-green-blue (RGB) values in the actual bitmap data area. The colors in the table should appear in order of importance. This helps a display driver render a bitmap on a device that cannot display as many colors as there are in the bitmap. If the DIB is in Windows version 3.0 or later format, the driver can use the biClrImportant member of the BITMAPINFOHEADER structure to determine which colors are important. The BITMAPINFO structure can be used to represent a combined bitmap-information header and color table. The bitmap bits, immediately following the color table, consist of an array of BYTE values representing consecutive rows, or "scan lines," of the bitmap. Each scan line consists of consecutive bytes representing the pixels in the scan line, in left-to-right order. The number of bytes representing a scan line depends on the color format and the width, in pixels, of the bitmap. If necessary, a scan line must be zero-padded to end on a 32-bit boundary. However, segment boundaries can appear anywhere in the bitmap. The scan lines in the bitmap are stored from bottom up. This means that the first byte in the array represents the pixels in the lower-left corner of the bitmap and the last byte represents the pixels in the upper-right corner. The biBitCount member of the BITMAPINFOHEADER structure determines the number of bits that define each pixel and the maximum number of colors in the bitmap. These members can have any of the following values: Value Meaning 1 Bitmap is monochrome and the color table contains two entries. Each bit in the bitmap array represents a pixel. If the bit is clear, the pixel is displayed with the color of the first entry in the color table. If the bit is set, the pixel has the color of the second entry in the table. 4 Bitmap has a maximum of 16 colors. Each pixel in the bitmap is represented by a 4-bit index into the color table. For example, if the first byte in the bitmap is 0x1F, the byte represents two pixels. The first pixel contains the color in the second table entry, and the second pixel contains the color in the sixteenth table entry. 8 Bitmap has a maximum of 256 colors. Each pixel in the bitmap is represented by a 1-byte index into the color table. For example, if the first byte in the bitmap is 0x1F, the first pixel has the color of the thirty-second table entry. 24 Bitmap has a maximum of 2^24 colors. The bmiColors (or bmciColors) member is NULL, and each 3-byte sequence in the bitmap array represents the relative intensities of red, green, and blue, respectively, for a pixel. The biClrUsed member of the BITMAPINFOHEADER structure specifies the number of color indexes in the color table actually used by the bitmap. If the biClrUsed member is set to zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member. An alternative form of bitmap file uses the BITMAPCOREINFO, BITMAPCOREHEADER, and RGBTRIPLE structures. Bitmap Compression Windows versions 3.0 and later support run-length encoded (RLE) formats for compressing bitmaps that use 4 bits per pixel and 8 bits per pixel. Compression reduces the disk and memory storage required for a bitmap. Compression of 8-Bits-per-Pixel Bitmaps When the biCompression member of the BITMAPINFOHEADER structure is set to BI_RLE8, the DIB is compressed using a run-length encoded format for a 256-color bitmap. This format uses two modes: encoded mode and absolute mode. Both modes can occur anywhere throughout a single bitmap. Encoded Mode A unit of information in encoded mode consists of two bytes. The first byte specifies the number of consecutive pixels to be drawn using the color index contained in the second byte. The first byte of the pair can be set to zero to indicate an escape that denotes the end of a line, the end of the bitmap, or a delta. The interpretation of the escape depends on the value of the second byte of the pair, which must be in the range 0x00 through 0x02. Following are the meanings of the escape values that can be used in the second byte: Second byte Meaning 0 End of line. 1 End of bitmap. 2 Delta. The two bytes following the escape contain unsigned values indicating the horizontal and vertical offsets of the next pixel from the current position. Absolute Mode Absolute mode is signaled by the first byte in the pair being set to zero and the second byte to a value between 0x03 and 0xFF. The second byte represents the number of bytes that follow, each of which contains the color index of a single pixel. Each run must be aligned on a word boundary. Following is an example of an 8-bit RLE bitmap (the two-digit hexadecimal values in the second column represent a color index for a single pixel): Compressed data Expanded data 03 04 04 04 04 05 06 06 06 06 06 06 00 03 45 56 67 00 45 56 67 02 78 78 78 00 02 05 01 Move 5 right and 1 down 02 78 78 78 00 00 End of line 09 1E 1E 1E 1E 1E 1E 1E 1E 1E 1E 00 01 End of RLE bitmap Compression of 4-Bits-per-Pixel Bitmaps When the biCompression member of the BITMAPINFOHEADER structure is set to BI_RLE4, the DIB is compressed using a run-length encoded format for a 16-color bitmap. This format uses two modes: encoded mode and absolute mode. Encoded Mode A unit of information in encoded mode consists of two bytes. The first byte of the pair contains the number of pixels to be drawn using the color indexes in the second byte. The second byte contains two color indexes, one in its high-order nibble (that is, its low-order 4 bits) and one in its low-order nibble. The first pixel is drawn using the color specified by the high-order nibble, the second is drawn using the color in the low-order nibble, the third is drawn with the color in the high-order nibble, and so on, until all the pixels specified by the first byte have been drawn. The first byte of the pair can be set to zero to indicate an escape that denotes the end of a line, the end of the bitmap, or a delta. The interpretation of the escape depends on the value of the second byte of the pair. In encoded mode, the second byte has a value in the range 0x00 through 0x02. The meaning of these values is the same as for a DIB with 8 bits per pixel. Absolute Mode In absolute mode, the first byte contains zero, the second byte contains the number of color indexes that follow, and subsequent bytes contain color indexes in their high- and low-order nibbles, one color index for each pixel. Each run must be aligned on a word boundary. Following is an example of a 4-bit RLE bitmap (the one-digit hexadecimal values in the second column represent a color index for a single pixel): Compressed data Expanded data 03 04 0 4 0 05 06 0 6 0 6 0 00 06 45 56 67 00 4 5 5 6 6 7 04 78 7 8 7 8 00 02 05 01 Move 5 right and 1 down 04 78 7 8 7 8 00 00 End of line 09 1E 1 E 1 E 1 E 1 E 1 00 01 End of RLE bitmap Bitmap Example The following example is a text dump of a 16-color bitmap (4 bits per pixel): Win3DIBFile BitmapFileHeader Type 19778 Size 3118 Reserved1 0 Reserved2 0 OffsetBits 118 BitmapInfoHeader Size 40 Width 80 Height 75 Planes 1 BitCount 4 Compression 0 SizeImage 3000 XPelsPerMeter 0 YPelsPerMeter 0 ColorsUsed 16 ColorsImportant 16 Win3ColorTable Blue Green Red Unused [00000000] 84 252 84 0 [00000001] 252 252 84 0 [00000002] 84 84 252 0 [00000003] 252 84 252 0 [00000004] 84 252 252 0 [00000005] 252 252 252 0 [00000006] 0 0 0 0 [00000007] 168 0 0 0 [00000008] 0 168 0 0 [00000009] 168 168 0 0 [0000000A] 0 0 168 0 [0000000B] 168 0 168 0 [0000000C] 0 168 168 0 [0000000D] 168 168 168 0 [0000000E] 84 84 84 0 [0000000F] 252 84 84 0 Image . . Bitmap data . Icon-Resource File Format An icon-resource file contains image data for icons used by Windows applications. The file consists of an icon directory identifying the number and types of icon images in the file, plus one or more icon images. The default filename extension for an icon-resource file is .ICO. Icon Directory Each icon-resource file starts with an icon directory. The icon directory, defined as an ICONDIR structure, specifies the number of icons in the resource and the dimensions and color format of each icon image. The ICONDIR structure has the following form: typedef struct ICONDIR { WORD idReserved; WORD idType; WORD idCount; ICONDIRENTRY idEntries[1]; } ICONHEADER; Following are the members in the ICONDIR structure: idReserved Reserved; must be zero. idType Specifies the resource type. This member is set to 1. idCount Specifies the number of entries in the directory. idEntries Specifies an array of ICONDIRENTRY structures containing information about individual icons. The idCount member specifies the number of structures in the array. The ICONDIRENTRY structure specifies the dimensions and color format for an icon. The structure has the following form: struct IconDirectoryEntry { BYTE bWidth; BYTE bHeight; BYTE bColorCount; BYTE bReserved; WORD wPlanes; WORD wBitCount; DWORD dwBytesInRes; DWORD dwImageOffset; }; Following are the members in the ICONDIRENTRY structure: bWidth Specifies the width of the icon, in pixels. Acceptable values are 16, 32, and 64. bHeight Specifies the height of the icon, in pixels. Acceptable values are 16, 32, and 64. bColorCount Specifies the number of colors in the icon. Acceptable values are 2, 8, and 16. bReserved Reserved; must be zero. wPlanes Specifies the number of color planes in the icon bitmap. wBitCount Specifies the number of bits in the icon bitmap. dwBytesInRes Specifies the size of the resource, in bytes. dwImageOffset Specifies the offset, in bytes, from the beginning of the file to the icon image. Icon Image Each icon-resource file contains one icon image for each image identified in the icon directory. An icon image consists of an icon-image header, a color table, an XOR mask, and an AND mask. The icon image has the following form: BITMAPINFOHEADER icHeader; RGBQUAD icColors[]; BYTE icXOR[]; BYTE icAND[]; The icon-image header, defined as a BITMAPINFOHEADER structure, specifies the dimensions and color format of the icon bitmap. Only the biSize through biBitCount members and the biSizeImage member are used. All other members (such as biCompression and biClrImportant) must be set to zero. The color table, defined as an array of RGBQUAD structures, specifies the colors used in the XOR mask. As with the color table in a bitmap file, the biBitCount member in the icon-image header determines the number of elements in the array. For more information about the color table, see Section 1.1, "Bitmap-File Formats." The XOR mask, immediately following the color table, is an array of BYTE values representing consecutive rows of a bitmap. The bitmap defines the basic shape and color of the icon image. As with the bitmap bits in a bitmap file, the bitmap data in an icon-resource file is organized in scan lines, with each byte representing one or more pixels, as defined by the color format. For more information about these bitmap bits, see Section 1.1, "Bitmap-File Formats." The AND mask, immediately following the XOR mask, is an array of BYTE values, representing a monochrome bitmap with the same width and height as the XOR mask. The array is organized in scan lines, with each byte representing 8 pixels. When Windows draws an icon, it uses the AND and XOR masks to combine the icon image with the pixels already on the display surface. Windows first applies the AND mask by using a bitwise AND operation; this preserves or removes existing pixel color. Windows then applies the XOR mask by using a bitwise XOR operation. This sets the final color for each pixel. The following illustration shows the XOR and AND masks that create a monochrome icon (measuring 8 pixels by 8 pixels) in the form of an uppercase K: Windows Icon Selection Windows detects the resolution of the current display and matches it against the width and height specified for each version of the icon image. If Windows determines that there is an exact match between an icon image and the current device, it uses the matching image. Otherwise, it selects the closest match and stretches the image to the proper size. If an icon-resource file contains more than one image for a particular resolution, Windows uses the icon image that most closely matches the color capabilities of the current display. If no image matches the device capabilities exactly, Windows selects the image that has the greatest number of colors without exceeding the number of display colors. If all images exceed the color capabilities of the current display, Windows uses the icon image with the least number of colors. Cursor-Resource File Format A cursor-resource file contains image data for cursors used by Windows applications. The file consists of a cursor directory identifying the number and types of cursor images in the file, plus one or more cursor images. The default filename extension for a cursor-resource file is .CUR. Cursor Directory Each cursor-resource file starts with a cursor directory. The cursor directory, defined as a CURSORDIR structure, specifies the number of cursors in the file and the dimensions and color format of each cursor image. The CURSORDIR structure has the following form: typedef struct _CURSORDIR { WORD cdReserved; WORD cdType; WORD cdCount; CURSORDIRENTRY cdEntries[]; } CURSORDIR; Following are the members in the CURSORDIR structure: cdReserved Reserved; must be zero. cdType Specifies the resource type. This member must be set to 2. cdCount Specifies the number of cursors in the file. cdEntries Specifies an array of CURSORDIRENTRY structures containing information about individual cursors. The cdCount member specifies the number of structures in the array. A CURSORDIRENTRY structure specifies the dimensions and color format of a cursor image. The structure has the following form: typedef struct _CURSORDIRENTRY { BYTE bWidth; BYTE bHeight; BYTE bColorCount; BYTE bReserved; WORD wXHotspot; WORD wYHotspot; DWORD lBytesInRes; DWORD dwImageOffset; } CURSORDIRENTRY; Following are the members in the CURSORDIRENTRY structure: bWidth Specifies the width of the cursor, in pixels. bHeight Specifies the height of the cursor, in pixels. bColorCount Reserved; must be zero. bReserved Reserved; must be zero. wXHotspot Specifies the x-coordinate, in pixels, of the hot spot. wYHotspot Specifies the y-coordinate, in pixels, of the hot spot. lBytesInRes Specifies the size of the resource, in bytes. dwImageOffset Specifies the offset, in bytes, from the start of the file to the cursor image. Cursor Image Each cursor-resource file contains one cursor image for each image identified in the cursor directory. A cursor image consists of a cursor-image header, a color table, an XOR mask, and an AND mask. The cursor image has the following form: BITMAPINFOHEADER crHeader; RGBQUAD crColors[]; BYTE crXOR[]; BYTE crAND[]; The cursor hot spot is a single pixel in the cursor bitmap that Windows uses to track the cursor. The crXHotspot and crYHotspot members specify the x- and y-coordinates of the cursor hot spot. These coordinates are 16-bit integers. The cursor-image header, defined as a BITMAPINFOHEADER structure, specifies the dimensions and color format of the cursor bitmap. Only the biSize through biBitCount members and the biSizeImage member are used. The biHeight member specifies the combined height of the XOR and AND masks for the cursor. This value is twice the height of the XOR mask. The biPlanes and biBitCount members must be 1. All other members (such as biCompression and biClrImportant) must be set to zero. The color table, defined as an array of RGBQUAD structures, specifies the colors used in the XOR mask. For a cursor image, the table contains exactly two structures, since the biBitCount member in the cursor-image header is always 1. The XOR mask, immediately following the color table, is an array of BYTE values representing consecutive rows of a bitmap. The bitmap defines the basic shape and color of the cursor image. As with the bitmap bits in a bitmap file, the bitmap data in a cursor-resource file is organized in scan lines, with each byte representing one or more pixels, as defined by the color format. For more information about these bitmap bits, see Section 1.1, "Bitmap-File Formats." The AND mask, immediately following the XOR mask, is an array of BYTE values representing a monochrome bitmap with the same width and height as the XOR mask. The array is organized in scan lines, with each byte representing 8 pixels. When Windows draws a cursor, it uses the AND and XOR masks to combine the cursor image with the pixels already on the display surface. Windows first applies the AND mask by using a bitwise AND operation; this preserves or removes existing pixel color. Window then applies the XOR mask by using a bitwise XOR operation. This sets the final color for each pixel. The following illustration shows the XOR and the AND masks that create a cursor (measuring 8 pixels by 8 pixels) in the form of an arrow: Following are the bit-mask values necessary to produce black, white, inverted, and transparent results: Pixel result AND maskXOR mask Black 0 0 White 0 1 Transparent 1 0 Inverted1 1 Windows Cursor Selection If a cursor-resource file contains more than one cursor image, Windows determines the best match for a particular display by examining the width and height of the cursor images. ============================================================================== BITMAPFILEHEADER (3.0) typedef struct tagBITMAPFILEHEADER { /* bmfh */ UINT bfType; DWORD bfSize; UINT bfReserved1; UINT bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; The BITMAPFILEHEADER structure contains information about the type, size, and layout of a device-independent bitmap (DIB) file. Member Description bfType Specifies the type of file. This member must be BM. bfSize Specifies the size of the file, in bytes. bfReserved1 Reserved; must be set to zero. bfReserved2 Reserved; must be set to zero. bfOffBits Specifies the byte offset from the BITMAPFILEHEADER structure to the actual bitmap data in the file. Comments A BITMAPINFO or BITMAPCOREINFO structure immediately follows the BITMAPFILEHEADER structure in the DIB file. See Also BITMAPCOREINFO, BITMAPINFO ============================================================================== BITMAPINFO (3.0) typedef struct tagBITMAPINFO { /* bmi */ BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO; The BITMAPINFO structure fully defines the dimensions and color information for a Windows 3.0 or later device-independent bitmap (DIB). Member Description bmiHeader Specifies a BITMAPINFOHEADER structure that contains information about the dimensions and color format of a DIB. bmiColors Specifies an array of RGBQUAD structures that define the colors in the bitmap. Comments A Windows 3.0 or later DIB consists of two distinct parts: a BITMAPINFO structure, which describes the dimensions and colors of the bitmap, and an array of bytes defining the pixels of the bitmap. The bits in the array are packed together, but each scan line must be zero-padded to end on a LONG boundary. Segment boundaries, however, can appear anywhere in the bitmap. The origin of the bitmap is the lower-left corner. The biBitCount member of the BITMAPINFOHEADER structure determines the number of bits which define each pixel and the maximum number of colors in the bitmap. This member may be set to any of the following values: Value Meaning 1 The bitmap is monochrome, and the bmciColors member must contain two entries. Each bit in the bitmap array represents a pixel. If the bit is clear, the pixel is displayed with the color of the first entry in the bmciColors table. If the bit is set, the pixel has the color of the second entry in the table. 4 The bitmap has a maximum of 16 colors, and the bmciColors member contains 16 entries. Each pixel in the bitmap is represented by a four-bit index into the color table. For example, if the first byte in the bitmap is 0x1F, the byte represents two pixels. The first pixel contains the color in the second table entry, and the second pixel contains the color in the sixteenth table entry. 8 The bitmap has a maximum of 256 colors, and the bmciColors member contains 256 entries. In this case, each byte in the array represents a single pixel. 24 The bitmap has a maximum of 2^24 colors. The bmciColors member is NULL, and each 3-byte sequence in the bitmap array represents the relative intensities of red, green, and blue, respectively, of a pixel. The biClrUsed member of the BITMAPINFOHEADER structure specifies the number of color indexes in the color table actually used by the bitmap. If the biClrUsed member is set to zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member. The colors in the bmiColors table should appear in order of importance. Alternatively, for functions that use DIBs, the bmiColors member can be an array of 16-bit unsigned integers that specify an index into the currently realized logical palette instead of explicit RGB values. In this case, an application using the bitmap must call DIB functions with the wUsage parameter set to DIB_PAL_COLORS. Note: The bmiColors member should not contain palette indexes if the bitmap is to be stored in a file or transferred to another application. Unless the application uses the bitmap exclusively and under its complete control, the bitmap color table should contain explicit RGB values. See Also BITMAPINFOHEADER, RGBQUAD ============================================================================== BITMAPINFOHEADER (3.0) typedef struct tagBITMAPINFOHEADER { /* bmih */ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; The BITMAPINFOHEADER structure contains information about the dimensions and color format of a Windows 3.0 or later device-independent bitmap (DIB). Member Description biSize Specifies the number of bytes required by the BITMAPINFOHEADER structure. biWidth Specifies the width of the bitmap, in pixels. biHeightSpecifies the height of the bitmap, in pixels. biPlanesSpecifies the number of planes for the target device. This member must be set to 1. biBitCount Specifies the number of bits per pixel. This value must be 1, 4, 8, or 24. biCompression Specifies the type of compression for a compressed bitmap. It can be one of the following values: Value Meaning BI_RGB Specifies that the bitmap is not compressed. BI_RLE8 Specifies a run-length encoded format for bitmaps with 8 bits per pixel. The compression format is a 2-byte format consisting of a count byte followed by a byte containing a color index. For more information, see the following Comments section. BI_RLE4 Specifies a run-length encoded format for bitmaps with 4 bits per pixel. The compression format is a 2-byte format consisting of a count byte followed by two word-length color indexes. For more information, see the following Comments section. biSizeImage Specifies the size, in bytes, of the image. It is valid to set this member to zero if the bitmap is in the BI_RGB format. biXPelsPerMeter Specifies the horizontal resolution, in pixels per meter, of the target device for the bitmap. An application can use this value to select a bitmap from a resource group that best matches the characteristics of the current device. biYPelsPerMeter Specifies the vertical resolution, in pixels per meter, of the target device for the bitmap. biClrUsed Specifies the number of color indexes in the color table actually used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member. For more information on the maximum sizes of the color table, see the description of the BITMAPINFO structure earlier in this topic. If the biClrUsed member is nonzero, it specifies the actual number of colors that the graphics engine or device driver will access if the biBitCount member is less than 24. If biBitCount is set to 24, biClrUsed specifies the size of the reference color table used to optimize performance of Windows color palettes. If the bitmap is a packed bitmap (that is, a bitmap in which the bitmap array immediately follows the BITMAPINFO header and which is referenced by a single pointer), the biClrUsed member must be set to zero or to the actual size of the color table. biClrImportant Specifies the number of color indexes that are considered important for displaying the bitmap. If this value is zero, all colors are important. Comments The BITMAPINFO structure combines the BITMAPINFOHEADER structure and a color table to provide a complete definition of the dimensions and colors of a Windows 3.0 or later DIB. For more information about specifying a Windows 3.0 DIB, see the description of the BITMAPINFO structure. An application should use the information stored in the biSize member to locate the color table in a BITMAPINFO structure as follows: pColor = ((LPSTR) pBitmapInfo + (WORD) (pBitmapInfo->bmiHeader.biSize)) Windows supports formats for compressing bitmaps that define their colors with 8 bits per pixel and with 4 bits per pixel. Compression reduces the disk and memory storage required for the bitmap. The following paragraphs describe these formats. BI_RLE8 When the biCompression member is set to BI_RLE8, the bitmap is compressed using a run-length encoding format for an 8-bit bitmap. This format may be compressed in either of two modes: encoded and absolute. Both modes can occur anywhere throughout a single bitmap. Encoded mode consists of two bytes: the first byte specifies the number of consecutive pixels to be drawn using the color index contained in the second byte. In addition, the first byte of the pair can be set to zero to indicate an escape that denotes an end of line, end of bitmap, or a delta. The interpretation of the escape depends on the value of the second byte of the pair. The following list shows the meaning of the second byte: Value Meaning 0 End of line. 1 End of bitmap. 2 Delta. The two bytes following the escape contain unsigned values indicating the horizontal and vertical offset of the next pixel from the current position. Absolute mode is signaled by the first byte set to zero and the second byte set to a value between 0x03 and 0xFF. In absolute mode, the second byte represents the number of bytes that follow, each of which contains the color index of a single pixel. When the second byte is set to 2 or less, the escape has the same meaning as in encoded mode. In absolute mode, each run must be aligned on a word boundary. The following example shows the hexadecimal values of an 8-bit compressed bitmap: 03 04 05 06 00 03 45 56 67 00 02 78 00 02 05 01 02 78 00 00 09 1E 00 01 This bitmap would expand as follows (two-digit values represent a color index for a single pixel): 04 04 04 06 06 06 06 06 45 56 67 78 78 move current position 5 right and 1 down 78 78 end of line 1E 1E 1E 1E 1E 1E 1E 1E 1E end of RLE bitmap BI_RLE4 When the biCompression member is set to BI_RLE4, the bitmap is compressed using a run-length encoding (RLE) format for a 4-bit bitmap, which also uses encoded and absolute modes. In encoded mode, the first byte of the pair contains the number of pixels to be drawn using the color indexes in the second byte. The second byte contains two color indexes, one in its high-order nibble (that is, its low-order four bits) and one in its low-order nibble. The first of the pixels is drawn using the color specified by the high-order nibble, the second is drawn using the color in the low-order nibble, the third is drawn with the color in the high-order nibble, and so on, until all the pixels specified by the first byte have been drawn. In absolute mode, the first byte contains zero, the second byte contains the number of color indexes that follow, and subsequent bytes contain color indexes in their high- and low-order nibbles, one color index for each pixel. In absolute mode, each run must be aligned on a word boundary. The end-of-line, end-of-bitmap, and delta escapes also apply to BI_RLE4. The following example shows the hexadecimal values of a 4-bit compressed bitmap: 03 04 05 06 00 06 45 56 67 00 04 78 00 02 05 01 04 78 00 00 09 1E 00 01 This bitmap would expand as follows (single-digit values represent a color index for a single pixel): 0 4 0 0 6 0 6 0 4 5 5 6 6 7 7 8 7 8 move current position 5 right and 1 down 7 8 7 8 end of line 1 E 1 E 1 E 1 E 1 end of RLE bitmap See Also BITMAPINFO ============================================================================== RGBQUAD (3.0) typedef struct tagRGBQUAD { /* rgbq */ BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; The RGBQUAD structure describes a color consisting of relative intensities of red, green, and blue. The bmiColors member of the BITMAPINFO structure consists of an array of RGBQUAD structures. Member Description rgbBlue Specifies the intensity of blue in the color. rgbGreenSpecifies the intensity of green in the color. rgbRed Specifies the intensity of red in the color. rgbReserved Not used; must be set to zero. See Also BITMAPINFO ============================================================================== RGB (2.x) COLORREF RGB(cRed, cGreen, cBlue) BYTE cRed; /* red component of color */ BYTE cGreen; /* green component of color */ BYTE cBlue; /* blue component of color */ The RGB macro selects an RGB color based on the parameters supplied and the color capabilities of the output device. Parameter Description cRed Specifies the intensity of the red color field. cGreen Specifies the intensity of the green color field. cBlue Specifies the intensity of the blue color field. Returns The return value specifies the resultant RGB color. Comments The intensity for each argument can range from 0 through 255. If all three intensities are specified as zero, the result is black. If all three intensities are specified as 255, the result is white. Comments The RGB macro is defined in WINDOWS.H as follows: #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8))| \ (((DWORD)(BYTE)(b))<<16))) See Also GetBValue, GetGValue, GetRValue, PALETTEINDEX, PALETTERGB ============================================================================== BITMAPCOREINFO (3.0) typedef struct tagBITMAPCOREINFO { /* bmci */ BITMAPCOREHEADER bmciHeader; RGBTRIPLE bmciColors[1]; } BITMAPCOREINFO; The BITMAPCOREINFO structure fully defines the dimensions and color information for a device-independent bitmap (DIB). Windows applications should use the BITMAPINFO structure instead of BITMAPCOREINFO whenever possible. Member Description bmciHeader Specifies a BITMAPCOREHEADER structure that contains information about the dimensions and color format of a DIB. bmciColors Specifies an array of RGBTRIPLE structures that define the colors in the bitmap. Comments The BITMAPCOREINFO structure describes the dimensions and colors of a bitmap. It is followed immediately in memory by an array of bytes which define the pixels of the bitmap. The bits in the array are packed together, but each scan line must be zero-padded to end on a LONG boundary. Segment boundaries, however, can appear anywhere in the bitmap. The origin of the bitmap is the lower-left corner. The bcBitCount member of the BITMAPCOREHEADER structure determines the number of bits that define each pixel and the maximum number of colors in the bitmap. This member may be set to any of the following values: Value Meaning 1 The bitmap is monochrome, and the bmciColors member must contain two entries. Each bit in the bitmap array represents a pixel. If the bit is clear, the pixel is displayed with the color of the first entry in the bmciColors table. If the bit is set, the pixel has the color of the second entry in the table. 4 The bitmap has a maximum of 16 colors, and the bmciColors member contains 16 entries. Each pixel in the bitmap is represented by a four-bit index into the color table. For example, if the first byte in the bitmap is 0x1F, the byte represents two pixels. The first pixel contains the color in the second table entry, and the second pixel contains the color in the sixteenth table entry. 8 The bitmap has a maximum of 256 colors, and the bmciColors member contains 256 entries. In this case, each byte in the array represents a single pixel. 24 The bitmap has a maximum of 2^24 colors. The bmciColors member is NULL, and each 3-byte sequence in the bitmap array represents the relative intensities of red, green, and blue, respectively, of a pixel. The colors in the bmciColors table should appear in order of importance. Alternatively, for functions that use DIBs, the bmciColors member can be an array of 16-bit unsigned integers that specify an index into the currently realized logical palette instead of explicit RGB values. In this case, an application using the bitmap must call DIB functions with the wUsage parameter set to DIB_PAL_COLORS. Note: The bmciColors member should not contain palette indexes if the bitmap is to be stored in a file or transferred to another application. Unless the application uses the bitmap exclusively and under its complete control, the bitmap color table should contain explicit RGB values. See Also BITMAPINFO, BITMAPCOREHEADER, RGBTRIPLE ============================================================================== BITMAPCOREHEADER (3.0) typedef struct tagBITMAPCOREHEADER { /* bmch */ DWORD bcSize; short bcWidth; short bcHeight; WORD bcPlanes; WORD bcBitCount; } BITMAPCOREHEADER; The BITMAPCOREHEADER structure contains information about the dimensions and color format of a device-independent bitmap (DIB). Windows applications should use the BITMAPINFOHEADER structure instead of BITMAPCOREHEADER whenever possible. Member Description bcSize Specifies the number of bytes required by the BITMAPCOREHEADER structure. bcWidth Specifies the width of the bitmap, in pixels. bcHeightSpecifies the height of the bitmap, in pixels. bcPlanesSpecifies the number of planes for the target device. This member must be set to 1. bcBitCount Specifies the number of bits per pixel. This value must be 1, 4, 8, or 24. Comments The BITMAPCOREINFO structure combines the BITMAPCOREHEADER structure and a color table to provide a complete definition of the dimensions and colors of a DIB. See the description of the BITMAPCOREINFO structure for more information about specifying a DIB. An application should use the information stored in the bcSize member to locate the color table in a BITMAPCOREINFO structure with a method such as the following: lpColor = ((LPSTR) pBitmapCoreInfo + (UINT) (pBitmapCoreInfo->bcSize)) See Also BITMAPCOREINFO, BITMAPINFOHEADER, BITMAPINFOHEADER ============================================================================= RGBTRIPLE (3.0) typedef struct tagRGBTRIPLE { /* rgbt */ BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } RGBTRIPLE; The RGBTRIPLE structure describes a color consisting of relative intensities of red, green, and blue. The bmciColors member of the BITMAPCOREINFO structure consists of an array of RGBTRIPLE structures. Windows applications should use the BITMAPINFO structure instead of BITMAPCOREINFO whenever possible. The BITMAPINFO structure uses an RGBQUAD structure instead of the RGBTRIPLE structure. Member Description rgbtBlueSpecifies the intensity of blue in the color. rgbtGreen Specifies the intensity of green in the color. rgbtRed Specifies the intensity of red in the color. See Also BITMAPCOREINFO, BITMAPINFO, RGBQUAD ============================================================================== LZW and GIF explained----Steve Blackstock I hope this little document will help enlighten those of you out there who want to know more about the Lempel-Ziv Welch compression algorithm, and, specifically, the implementation that GIF uses. Before we start, here's a little terminology, for the purposes of this document: "character": a fundamental data element. In normal text files, this is just a single byte. In raster images, which is what we're interested in, it's an index that specifies the color of a given pixel. I'll refer to an arbitray character as "K". "charstream": a stream of characters, as in a data file. "string": a number of continuous characters, anywhere from one to very many characters in length. I can specify an arbitrary string as "[...]K". "prefix": almost the same as a string, but with the implication that a prefix immediately precedes a character, and a prefix can have a length of zero. So, a prefix and a character make up a string. I will refer to an arbitrary prefix as "[...]". "root": a single-character string. For most purposes, this is a character, but we may occasionally make a distinction. It is [...]K, where [...] is empty. "code": a number, specified by a known number of bits, which maps to a string. "codestream": the output stream of codes, as in the "raster data" "entry": a code and its string. "string table": a list of entries; usually, but not necessarily, unique. That should be enough of that. LZW is a way of compressing data that takes advantage of repetition of strings in the data. Since raster data usually contains a lot of this repetition, LZW is a good way of compressing and decompressing it. For the moment, lets consider normal LZW encoding and decoding. GIF's variation on the concept is just an extension from there. LZW manipulates three objects in both compression and decompression: the charstream, the codestream, and the string table. In compression, the charstream is the input and the codestream is the output. In decompression, the codestream is the input and the charstream is the output. The string table is a product of both compression and decompression, but is never passed from one to the other. The first thing we do in LZW compression is initialize our string table. To do this, we need to choose a code size (how many bits) and know how many values our characters can possibly take. Let's say our code size is 12 bits, meaning we can store 0->FFF, or 4096 entries in our string table. Lets also say that we have 32 possible different characters. (This corresponds to, say, a picture in which there are 32 different colors possible for each pixel.) To initialize the table, we set code#0 to character#0, code #1 to character#1, and so on, until code#31 to character#31. Actually, we are specifying that each code from 0 to 31 maps to a root. There will be no more entries in the table that have this property. Now we start compressing data. Let's first define something called the "current prefix". It's just a prefix that we'll store things in and compare things to now and then. I will refer to it as "[.c.]". Initially, the current prefix has nothing in it. Let's also define a "current string", which will be the current prefix plus the next character in the charstream. I will refer to the current string as "[.c.]K", where K is some character. OK, look at the first character in the charstream. Call it P. Make [.c.]P the current string. (At this point, of course, it's just the root P.) Now search through the string table to see if [.c.]P appears in it. Of course, it does now, because our string table is initialized to have all roots. So we don't do anything. Now make [.c.]P the current prefix. Look at the next character in the charstream. Call it Q. Add it to the current prefix to form [.c.]Q, the current string. Now search through the string table to see if [.c.]Q appears in it. In this case, of course, it doesn't. Aha! Now we get to do something. Add [.c.]Q (which is PQ in this case) to the string table for code#32, and output the code for [.c.] to the codestream. Now start over again with the current prefix being just the root P. Keep adding characters to [.c.] to form [.c.]K, until you can't find [.c.]K in the string table. Then output the code for [.c.] and add [.c.]K to the string table. In pseudo-code, the algorithm goes something like this: [1] Initialize string table; [2] [.c.] <- empty; [3] K <- next character in charstream; [4] Is [.c.]K in string table? (yes: [.c.] <- [.c.]K; go to [3]; ) (no: add [.c.]K to the string table; output the code for [.c.] to the codestream; [.c.] <- K; go to [3]; ) It's as simple as that! Of course, when you get to step [3] and there aren't any more characters left, you just output the code for [.c.] and throw the table away. You're done. Wanna do an example? Let's pretend we have a four-character alphabet: A,B,C,D. The charstream looks like ABACABA. Let's compress it. First, we initialize our string table to: #0=A, #1=B, #2=C, #3=D. The first character is A, which is in the string table, so [.c.] becomes A. Next we get AB, which is not in the table, so we output code #0 (for [.c.]), and add AB to the string table as code #4. [.c.] becomes B. Next we get [.c.]A = BA, which is not in the string table, so output code #1, and add BA to the string table as code #5. [.c.] becomes A. Next we get AC, which is not in the string table. Output code #0, and add AC to the string table as code #6. Now [.c.] becomes C. Next we get [.c.]A = CA, which is not in the table. Output #2 for C, and add CA to table as code#7. Now [.c.] becomes A. Next we get AB, which IS in the string table, so [.c.] gets AB, and we look at ABA, which is not in the string table, so output the code for AB, which is #4, and add ABA to the string table as code #8. [.c.] becomes A. We can't get any more characters, so we just output #0 for the code for A, and we're done. So, the codestream is #0#1#0#2#4#0. A few words (four) should be said here about efficiency: use a hashing strategy. The search through the string table can be computationally intensive, and some hashing is well worth the effort. Also, note that "straight LZW" compression runs the risk of overflowing the string table - getting to a code which can't be represented in the number of bits you've set aside for codes. There are several ways of dealing with this problem, and GIF implements a very clever one, but we'll get to that. An important thing to notice is that, at any point during the compression, if [...]K is in the string table, [...] is there also. This fact suggests an efficient method for storing strings in the table. Rather than store the entire string of K's in the table, realize that any string can be expressed as a prefix plus a character: [...]K. If we're about to store [...]K in the table, we know that [...] is already there, so we can just store the code for [...] plus the final character K. Ok, that takes care of compression. Decompression is perhaps more difficult conceptually, but it is really easier to program. Here's how it goes: We again have to start with an initialized string table. This table comes from what knowledge we have about the charstream that we will eventually get, like what possible values the characters can take. In GIF files, this information is in the header as the number of possible pixel values. The beauty of LZW, though, is that this is all we need to know. We will build the rest of the string table as we decompress the codestream. The compression is done in such a way that we will never encounter a code in the codestream that we can't translate into a string. We need to define something called a "current code", which I will refer to as "", and an "old-code", which I will refer to as "". To start things off, look at the first code. This is now . This code will be in the intialized string table as the code for a root. Output the root to the charstream. Make this code the old-code . *Now look at the next code, and make it . It is possible that this code will not be in the string table, but let's assume for now that it is. Output the string corresponding to to the codestream. Now find the first character in the string you just translated. Call this K. Add this to the prefix [...] generated by to form a new string [...]K. Add this string [...]K to the string table, and set the old-code to the current code . Repeat from where I typed the asterisk, and you're all set. Read this paragraph again if you just skimmed it!!! Now let's consider the possibility that is not in the string table. Think back to compression, and try to understand what happens when you have a string like P[...]P[...]PQ appear in the charstream. Suppose P[...] is already in the string table, but P[...]P is not. The compressor will parse out P[...], and find that P[...]P is not in the string table. It will output the code for P[...], and add P[...]P to the string table. Then it will get up to P[...]P for the next string, and find that P[...]P is in the table, as the code just added. So it will output the code for P[...]P if it finds that P[...]PQ is not in the table. The decompressor is always "one step behind" the compressor. When the decompressor sees the code for P[...]P, it will not have added that code to it's string table yet because it needed the beginning character of P[...]P to add to the string for the last code, P[...], to form the code for P[...]P. However, when a decompressor finds a code that it doesn't know yet, it will always be the very next one to be added to the string table. So it can guess at what the string for the code should be, and, in fact, it will always be correct. If I am a decompressor, and I see code#124, and yet my string table has entries only up to code#123, I can figure out what code#124 must be, add it to my string table, and output the string. If code#123 generated the string, which I will refer to here as a prefix, [...], then code#124, in this special case, will be [...] plus the first character of [...]. So just add the first character of [...] to the end of itself. Not too bad. As an example (and a very common one) of this special case, let's assume we have a raster image in which the first three pixels have the same color value. That is, my charstream looks like: QQQ.... For the sake of argument, let's say we have 32 colors, and Q is the color#12. The compressor will generate the code sequence 12,32,.... (if you don't know why, take a minute to understand it.) Remember that #32 is not in the initial table, which goes from #0 to #31. The decompressor will see #12 and translate it just fine as color Q. Then it will see #32 and not yet know what that means. But if it thinks about it long enough, it can figure out that QQ should be entry#32 in the table and QQ should be the next string output. So the decompression pseudo-code goes something like: [1] Initialize string table; [2] get first code: ; [3] output the string for to the charstream; [4] = ; [5] <- next code in codestream; [6] does exist in the string table? (yes: output the string for to the charstream; [...] <- translation for ; K <- first character of translation for ; add [...]K to the string table; <- ; ) (no: [...] <- translation for ; K <- first character of [...]; output [...]K to charstream and add it to string table; <- ) [7] go to [5]; Again, when you get to step [5] and there are no more codes, you're finished. Outputting of strings, and finding of initial characters in strings are efficiency problems all to themselves, but I'm not going to suggest ways to do them here. Half the fun of programming is figuring these things out! --- Now for the GIF variations on the theme. In part of the header of a GIF file, there is a field, in the Raster Data stream, called "code size". This is a very misleading name for the field, but we have to live with it. What it is really is the "root size". The actual size, in bits, of the compression codes actually changes during compression/decompression, and I will refer to that size here as the "compression size". The initial table is just the codes for all the roots, as usual, but two special codes are added on top of those. Suppose you have a "code size", which is usually the number of bits per pixel in the image, of N. If the number of bits/pixel is one, then N must be 2: the roots take up slots #0 and #1 in the initial table, and the two special codes will take up slots #4 and #5. In any other case, N is the number of bits per pixel, and the roots take up slots #0 through #(2**N-1), and the special codes are (2**N) and (2**N + 1). The initial compression size will be N+1 bits per code. If you're encoding, you output the codes (N+1) bits at a time to start with, and if you're decoding, you grab (N+1) bits from the codestream at a time. As for the special codes: or the clear code, is (2**N), and , or end-of-information, is (2**N + 1). tells the compressor to re- initialize the string table, and to reset the compression size to (N+1). means there's no more in the codestream. If you're encoding or decoding, you should start adding things to the string table at + 2. If you're encoding, you should output as the very first code, and then whenever after that you reach code #4095 (hex FFF), because GIF does not allow compression sizes to be greater than 12 bits. If you're decoding, you should reinitialize your string table when you observe . The variable compression sizes are really no big deal. If you're encoding, you start with a compression size of (N+1) bits, and, whenever you output the code (2**(compression size)-1), you bump the compression size up one bit. So the next code you output will be one bit longer. Remember that the largest compression size is 12 bits, corresponding to a code of 4095. If you get that far, you must output as the next code, and start over. If you're decoding, you must increase your compression size AS SOON AS YOU write entry #(2**(compression size) - 1) to the string table. The next code you READ will be one bit longer. Don't make the mistake of waiting until you need to add the code (2**compression size) to the table. You'll have already missed a bit from the last code. The packaging of codes into a bitsream for the raster data is also a potential stumbling block for the novice encoder or decoder. The lowest order bit in the code should coincide with the lowest available bit in the first available byte in the codestream. For example, if you're starting with 5-bit compression codes, and your first three codes are, say, , , , where e, j, and o are bit#0, then your codestream will start off like: byte#0: hijabcde byte#1: .klmnofg So the differences between straight LZW and GIF LZW are: two additional special codes and variable compression sizes. If you understand LZW, and you understand those variations, you understand it all! Just as sort of a P.S., you may have noticed that a compressor has a little bit of flexibility at compression time. I specified a "greedy" approach to the compression, grabbing as many characters as possible before outputting codes. This is, in fact, the standard LZW way of doing things, and it will yield the best compression ratio. But there's no rule saying you can't stop anywhere along the line and just output the code for the current prefix, whether it's already in the table or not, and add that string plus the next character to the string table. There are various reasons for wanting to do this, especially if the strings get extremely long and make hashing difficult. If you need to, do it. Hope this helps out.----steve blackstock --------------------------------------------------------------------------- Article 5729 of comp.graphics: Path: polya!shelby!labrea!agate!ucbvax!tut.cis.ohio-state.edu!rutgers!cmcl2!phri!cooper!john >From: john@cooper.cooper.EDU (John Barkaus) Newsgroups: comp.graphics Subject: GIF file format responses 4/5 Keywords: GIF LZW Message-ID: <1489@cooper.cooper.EDU> Date: 21 Apr 89 20:56:35 GMT Organization: The Cooper Union (NY, NY) Lines: 1050 >From: cmcl2!neuron1.Jpl.Nasa.Gov!harry (Harry Langenbacher) G I F (tm) Graphics Interchange Format (tm) A standard defining a mechanism for the storage and transmission of raster-based graphics information June 15, 1987 (c) CompuServe Incorporated, 1987 All rights reserved While this document is copyrighted, the information contained within is made available for use in computer software without royalties, or licensing restrictions. GIF and 'Graphics Interchange Format' are trademarks of CompuServe, Incorporated. an H&R Block Company 5000 Arlington Centre Blvd. Columbus, Ohio 43220 (614) 457-8600 Page 2 Graphics Interchange Format (GIF) Specification Table of Contents INTRODUCTION . . . . . . . . . . . . . . . . . page 3 GENERAL FILE FORMAT . . . . . . . . . . . . . page 3 GIF SIGNATURE . . . . . . . . . . . . . . . . page 4 SCREEN DESCRIPTOR . . . . . . . . . . . . . . page 4 GLOBAL COLOR MAP . . . . . . . . . . . . . . . page 5 IMAGE DESCRIPTOR . . . . . . . . . . . . . . . page 6 LOCAL COLOR MAP . . . . . . . . . . . . . . . page 7 RASTER DATA . . . . . . . . . . . . . . . . . page 7 GIF TERMINATOR . . . . . . . . . . . . . . . . page 8 GIF EXTENSION BLOCKS . . . . . . . . . . . . . page 8 APPENDIX A - GLOSSARY . . . . . . . . . . . . page 9 APPENDIX B - INTERACTIVE SEQUENCES . . . . . . page 10 APPENDIX C - IMAGE PACKAGING & COMPRESSION . . page 12 APPENDIX D - MULTIPLE IMAGE PROCESSING . . . . page 15 Graphics Interchange Format (GIF) Page 3 Specification INTRODUCTION 'GIF' (tm) is CompuServe's standard for defining generalized color raster images. This 'Graphics Interchange Format' (tm) allows high-quality, high-resolution graphics to be displayed on a variety of graphics hardware and is intended as an exchange and display mechanism for graphics images. The image format described in this document is designed to support current and future image technology and will in addition serve as a basis for future CompuServe graphics products. The main focus of this document is to provide the technical information necessary for a programmer to implement GIF encoders and decoders. As such, some assumptions are made as to terminology relavent to graphics and programming in general. The first section of this document describes the GIF data format and its components and applies to all GIF decoders, either as standalone programs or as part of a communications package. Appendix B is a section relavent to decoders that are part of a communications software package and describes the protocol requirements for entering and exiting GIF mode, and responding to host interrogations. A glossary in Appendix A defines some of the terminology used in this document. Appendix C gives a detailed explanation of how the graphics image itself is packaged as a series of data bytes. Graphics Interchange Format Data Definition GENERAL FILE FORMAT +-----------------------+ | +-------------------+ | | | GIF Signature | | | +-------------------+ | | +-------------------+ | | | Screen Descriptor | | | +-------------------+ | | +-------------------+ | | | Global Color Map | | | +-------------------+ | . . . . . . | +-------------------+ | ---+ | | Image Descriptor | | | | +-------------------+ | | | +-------------------+ | | | | Local Color Map | | |- Repeated 1 to n times | +-------------------+ | | | +-------------------+ | | | | Raster Data | | | | +-------------------+ | ---+ . . . . . . |- GIF Terminator -| +-----------------------+ Graphics Interchange Format (GIF) Page 4 Specification GIF SIGNATURE The following GIF Signature identifies the data following as a valid GIF image stream. It consists of the following six characters: G I F 8 7 a The last three characters '87a' may be viewed as a version number for this particular GIF definition and will be used in general as a reference in documents regarding GIF that address any version dependencies. SCREEN DESCRIPTOR The Screen Descriptor describes the overall parameters for all GIF images following. It defines the overall dimensions of the image space or logical screen required, the existance of color mapping information, background screen color, and color depth information. This information is stored in a series of 8-bit bytes as described below. bits 7 6 5 4 3 2 1 0 Byte # +---------------+ | | 1 +-Screen Width -+ Raster width in pixels (LSB first) | | 2 +---------------+ | | 3 +-Screen Height-+ Raster height in pixels (LSB first) | | 4 +-+-----+-+-----+ M = 1, Global color map follows Descriptor |M| cr |0|pixel| 5 cr+1 = # bits of color resolution +-+-----+-+-----+ pixel+1 = # bits/pixel in image | background | 6 background=Color index of screen background +---------------+ (color is defined from the Global color |0 0 0 0 0 0 0 0| 7 map or default map if none specified) +---------------+ The logical screen width and height can both be larger than the physical display. How images larger than the physical display are handled is implementation dependent and can take advantage of hardware characteristics (e.g. Macintosh scrolling windows). Otherwise images can be clipped to the edges of the display. The value of 'pixel' also defines the maximum number of colors within an image. The range of values for 'pixel' is 0 to 7 which represents 1 to 8 bits. This translates to a range of 2 (B & W) to 256 colors. Bit 3 of word 5 is reserved for future definition and must be zero. Graphics Interchange Format (GIF) Page 5 Specification GLOBAL COLOR MAP The Global Color Map is optional but recommended for images where accurate color rendition is desired. The existence of this color map is indicated in the 'M' field of byte 5 of the Screen Descriptor. A color map can also be associated with each image in a GIF file as described later. However this global map will normally be used because of hardware restrictions in equipment available today. In the individual Image Descriptors the 'M' flag will normally be zero. If the Global Color Map is present, it's definition immediately follows the Screen Descriptor. The number of color map entries following a Screen Descriptor is equal to 2**(# bits per pixel), where each entry consists of three byte values representing the relative intensities of red, green and blue respectively. The structure of the Color Map block is: bits 7 6 5 4 3 2 1 0 Byte # +---------------+ | red intensity | 1 Red value for color index 0 +---------------+ |green intensity| 2 Green value for color index 0 +---------------+ | blue intensity| 3 Blue value for color index 0 +---------------+ | red intensity | 4 Red value for color index 1 +---------------+ |green intensity| 5 Green value for color index 1 +---------------+ | blue intensity| 6 Blue value for color index 1 +---------------+ : : (Continues for remaining colors) Each image pixel value received will be displayed according to its closest match with an available color of the display based on this color map. The color components represent a fractional intensity value from none (0) to full (255). White would be represented as (255,255,255), black as (0,0,0) and medium yellow as (180,180,0). For display, if the device supports fewer than 8 bits per color component, the higher order bits of each component are used. In the creation of a GIF color map entry with hardware supporting fewer than 8 bits per component, the component values for the hardware should be converted to the 8-bit format with the following calculation: = *255/(2** -1) This assures accurate translation of colors for all displays. In the cases of creating GIF images from hardware without color palette capability, a fixed palette should be created based on the available display colors for that hardware. If no Global Color Map is indicated, a default color map is generated internally which maps each possible incoming color index to the same hardware color index modulo where is the number of available hardware colors. Graphics Interchange Format (GIF) Page 6 Specification IMAGE DESCRIPTOR The Image Descriptor defines the actual placement and extents of the following image within the space defined in the Screen Descriptor. Also defined are flags to indicate the presence of a local color lookup map, and to define the pixel display sequence. Each Image Descriptor is introduced by an image separator character. The role of the Image Separator is simply to provide a synchronization character to introduce an Image Descriptor. This is desirable if a GIF file happens to contain more than one image. This character is defined as 0x2C hex or ',' (comma). When this character is encountered between images, the Image Descriptor will follow immediately. Any characters encountered between the end of a previous image and the image separator character are to be ignored. This allows future GIF enhancements to be present in newer image formats and yet ignored safely by older software decoders. bits 7 6 5 4 3 2 1 0 Byte # +---------------+ |0 0 1 0 1 1 0 0| 1 ',' - Image separator character +---------------+ | | 2 Start of image in pixels from the +- Image Left -+ left side of the screen (LSB first) | | 3 +---------------+ | | 4 +- Image Top -+ Start of image in pixels from the | | 5 top of the screen (LSB first) +---------------+ | | 6 +- Image Width -+ Width of the image in pixels (LSB first) | | 7 +---------------+ | | 8 +- Image Height-+ Height of the image in pixels (LSB first) | | 9 +-+-+-+-+-+-----+ M=0 - Use global color map, ignore 'pixel' |M|I|0|0|0|pixel| 10 M=1 - Local color map follows, use 'pixel' +-+-+-+-+-+-----+ I=0 - Image formatted in Sequential order I=1 - Image formatted in Interlaced order pixel+1 - # bits per pixel for this image The specifications for the image position and size must be confined to the dimensions defined by the Screen Descriptor. On the other hand it is not necessary that the image fill the entire screen defined. LOCAL COLOR MAP Graphics Interchange Format (GIF) Page 7 Specification A Local Color Map is optional and defined here for future use. If the 'M' bit of byte 10 of the Image Descriptor is set, then a color map follows the Image Descriptor that applies only to the following image. At the end of the image, the color map will revert to that defined after the Screen Descriptor. Note that the 'pixel' field of byte 10 of the Image Descriptor is used only if a Local Color Map is indicated. This defines the parameters not only for the image pixel size, but determines the number of color map entries that follow. The bits per pixel value will also revert to the value specified in the Screen Descriptor when processing of the image is complete. RASTER DATA The format of the actual image is defined as the series of pixel color index values that make up the image. The pixels are stored left to right sequentially for an image row. By default each image row is written sequentially, top to bottom. In the case that the Interlace or 'I' bit is set in byte 10 of the Image Descriptor then the row order of the image display follows a four-pass process in which the image is filled in by widely spaced rows. The first pass writes every 8th row, starting with the top row of the image window. The second pass writes every 8th row starting at the fifth row from the top. The third pass writes every 4th row starting at the third row from the top. The fourth pass completes the image, writing every other row, starting at the second row from the top. A graphic description of this process follows: Image Row Pass 1 Pass 2 Pass 3 Pass 4 Result --------------------------------------------------- 0 **1a** **1a** 1 **4a** **4a** 2 **3a** **3a** 3 **4b** **4b** 4 **2a** **2a** 5 **4c** **4c** 6 **3b** **3b** 7 **4d** **4d** 8 **1b** **1b** 9 **4e** **4e** 10 **3c** **3c** 11 **4f** **4f** 12 **2b** **2b** . . . The image pixel values are processed as a series of color indices which map into the existing color map. The resulting color value from the map is what is actually displayed. This series of pixel indices, the number of which is equal to image-width*image-height pixels, are passed to the GIF image data stream one value per pixel, compressed and packaged according to a version of the LZW compression algorithm as defined in Appendix C. Graphics Interchange Format (GIF) Page 8 Specification GIF TERMINATOR In order to provide a synchronization for the termination of a GIF image file, a GIF decoder will process the end of GIF mode when the character 0x3B hex or ';' is found after an image has been processed. By convention the decoding software will pause and wait for an action indicating that the user is ready to continue. This may be a carriage return entered at the keyboard or a mouse click. For interactive applications this user action must be passed on to the host as a carriage return character so that the host application can continue. The decoding software will then typically leave graphics mode and resume any previous process. GIF EXTENSION BLOCKS To provide for orderly extension of the GIF definition, a mechanism for defining the packaging of extensions within a GIF data stream is necessary. Specific GIF extensions are to be defined and documented by CompuServe in order to provide a controlled enhancement path. GIF Extension Blocks are packaged in a manner similar to that used by the raster data though not compressed. The basic structure is: 7 6 5 4 3 2 1 0 Byte # +---------------+ |0 0 1 0 0 0 0 1| 1 '!' - GIF Extension Block Introducer +---------------+ | function code | 2 Extension function code (0 to 255) +---------------+ ---+ | byte count | | +---------------+ | : : +-- Repeated as many times as necessary |func data bytes| | : : | +---------------+ ---+ . . . . . . +---------------+ |0 0 0 0 0 0 0 0| zero byte count (terminates block) +---------------+ A GIF Extension Block may immediately preceed any Image Descriptor or occur before the GIF Terminator. All GIF decoders must be able to recognize the existence of GIF Extension Blocks and read past them if unable to process the function code. This ensures that older decoders will be able to process extended GIF image files in the future, though without the additional functionality. Graphics Interchange Format (GIF) Page 9 Appendix A - Glossary GLOSSARY Pixel - The smallest picture element of a graphics image. This usually corresponds to a single dot on a graphics screen. Image resolution is typically given in units of pixels. For example a fairly standard graphics screen format is one 320 pixels across and 200 pixels high. Each pixel can appear as one of several colors depending on the capabilities of the graphics hardware. Raster - A horizontal row of pixels representing one line of an image. A typical method of working with images since most hardware is oriented to work most efficiently in this manner. LSB - Least Significant Byte. Refers to a convention for two byte numeric values in which the less significant byte of the value preceeds the more significant byte. This convention is typical on many microcomputers. Color Map - The list of definitions of each color used in a GIF image. These desired colors are converted to available colors through a table which is derived by assigning an incoming color index (from the image) to an output color index (of the hardware). While the color map definitons are specified in a GIF image, the output pixel colors will vary based on the hardware used and its ability to match the defined color. Interlace - The method of displaying a GIF image in which multiple passes are made, outputting raster lines spaced apart to provide a way of visualizing the general content of an entire image before all of the data has been processed. B Protocol - A CompuServe-developed error-correcting file transfer protocol available in the public domain and implemented in CompuServe VIDTEX products. This error checking mechanism will be used in transfers of GIF images for interactive applications. LZW - A sophisticated data compression algorithm based on work done by Lempel-Ziv & Welch which has the feature of very efficient one-pass encoding and decoding. This allows the image to be decompressed and displayed at the same time. The original article from which this technique was adapted is: Terry A. Welch, "A Technique for High Performance Data Compression", IEEE Computer, vol 17 no 6 (June 1984) This basic algorithm is also used in the public domain ARC file compression utilities. The CompuServe adaptation of LZW for GIF is described in Appendix C. Graphics Interchange Format (GIF) Page 10 Appendix B - Interactive Sequences GIF Sequence Exchanges for an Interactive Environment The following sequences are defined for use in mediating control between a GIF sender and GIF receiver over an interactive communications line. These sequences do not apply to applications that involve downloading of static GIF files and are not considered part of a GIF file. GIF CAPABILITIES ENQUIRY The GCE sequence is issued from a host and requests an interactive GIF decoder to return a response message that defines the graphics parameters for the decoder. This involves returning information about available screen sizes, number of bits/color supported and the amount of color detail supported. The escape sequence for the GCE is defined as: ESC [ > 0 g (g is lower case, spaces inserted for clarity) (0x1B 0x5B 0x3E 0x30 0x67) GIF CAPABILITIES RESPONSE The GIF Capabilities Response message is returned by an interactive GIF decoder and defines the decoder's display capabilities for all graphics modes that are supported by the software. Note that this can also include graphics printers as well as a monitor screen. The general format of this message is: #version;protocol{;dev, width, height, color-bits, color-res}... '#' - GCR identifier character (Number Sign) version - GIF format version number; initially '87a' protocol='0' - No end-to-end protocol supported by decoder Transfer as direct 8-bit data stream. protocol='1' - Can use an error correction protocol to transfer GIF data interactively from the host directly to the display. dev = '0' - Screen parameter set follows dev = '1' - Printer parameter set follows width- Maximum supported display width in pixels height - Maximum supported display height in pixels color-bits - Number of bits per pixel supported. The number of supported colors is therefore 2**color-bits. color-res - Number of bits per color component supported in the hardware color palette. If color-res is '0' then no hardware palette table is available. Note that all values in the GCR are returned as ASCII decimal numbers and the message is terminated by a Carriage Return character. Graphics Interchange Format (GIF) Page 11 Appendix B - Interactive Sequences The following GCR message describes three standard EGA configurations with no printer; the GIF data stream can be processed within an error correcting protocol: #87a;1 ;0,320,200,4,0 ;0,640,200,2,2 ;0,640,350,4,2 ENTER GIF GRAPHICS MODE Two sequences are currently defined to invoke an interactive GIF decoder into action. The only difference between them is that different output media are selected. These sequences are: ESC [ > 1 g Display GIF image on screen (0x1B 0x5B 0x3E 0x31 0x67) ESC [ > 2 g Display image directly to an attached graphics printer. The image may optionally be displayed on the screen as well. (0x1B 0x5B 0x3E 0x32 0x67) Note that the 'g' character terminating each sequence is in lower case. INTERACTIVE ENVIRONMENT The assumed environment for the transmission of GIF image data from an interactive application is a full 8-bit data stream from host to micro. All 256 character codes must be transferrable. The establishing of an 8-bit data path for communications will normally be taken care of by the host application programs. It is however up to the receiving communications programs supporting GIF to be able to receive and pass on all 256 8-bit codes to the GIF decoder software. Graphics Interchange Format (GIF) Page 12 Appendix C - Image Packaging & Compression The Raster Data stream that represents the actual output image can be represented as: 7 6 5 4 3 2 1 0 +---------------+ | code size | +---------------+ ---+ |blok byte count| | +---------------+ | : : +-- Repeated as many times as necessary | data bytes | | : : | +---------------+ ---+ . . . . . . +---------------+ |0 0 0 0 0 0 0 0| zero byte count (terminates data stream) +---------------+ The conversion of the image from a series of pixel values to a transmitted or stored character stream involves several steps. In brief these steps are: 1. Establish the Code Size - Define the number of bits needed to represent the actual data. 2. Compress the Data - Compress the series of image pixels to a series of compression codes. 3. Build a Series of Bytes - Take the set of compression codes and convert to a string of 8-bit bytes. 4. Package the Bytes - Package sets of bytes into blocks preceeded by character counts and output. ESTABLISH CODE SIZE The first byte of the GIF Raster Data stream is a value indicating the minimum number of bits required to represent the set of actual pixel values. Normally this will be the same as the number of color bits. Because of some algorithmic constraints however, black & white images which have one color bit must be indicated as having a code size of 2. This code size value also implies that the compression codes must start out one bit longer. COMPRESSION The LZW algorithm converts a series of data values into a series of codes which may be raw values or a code designating a series of values. Using text characters as an analogy, the output code consists of a character or a code representing a string of characters. Graphics Interchange Format (GIF) Page 13 Appendix C - Image Packaging & Compression The LZW algorithm used in GIF matches algorithmically with the standard LZW algorithm with the following differences: 1. A special Clear code is defined which resets all compression/decompression parameters and tables to a start-up state. The value of this code is 2**. For example if the code size indicated was 4 (image was 4 bits/pixel) the Clear code value would be 16 (10000 binary). The Clear code can appear at any point in the image data stream and therefore requires the LZW algorithm to process succeeding codes as if a new data stream was starting. Encoders should output a Clear code as the first code of each image data stream. 2. An End of Information code is defined that explicitly indicates the end of the image data stream. LZW processing terminates when this code is encountered. It must be the last code output by the encoder for an image. The value of this code is +1. 3. The first available compression code value is +2. 4. The output codes are of variable length, starting at +1 bits per code, up to 12 bits per code. This defines a maximum code value of 4095 (hex FFF). Whenever the LZW code value would exceed the current code length, the code length is increased by one. The packing/unpacking of these codes must then be altered to reflect the new code length. BUILD 8-BIT BYTES Because the LZW compression used for GIF creates a series of variable length codes, of between 3 and 12 bits each, these codes must be reformed into a series of 8-bit bytes that will be the characters actually stored or transmitted. This provides additional compression of the image. The codes are formed into a stream of bits as if they were packed right to left and then picked off 8 bits at a time to be output. Assuming a character array of 8 bits per character and using 5 bit codes to be packed, an example layout would be similar to: byte n byte 5 byte 4 byte 3 byte 2 byte 1 +-.....-----+--------+--------+--------+--------+--------+ | and so on |hhhhhggg|ggfffffe|eeeedddd|dcccccbb|bbbaaaaa| +-.....-----+--------+--------+--------+--------+--------+ Note that the physical packing arrangement will change as the number of bits per compression code change but the concept remains the same. PACKAGE THE BYTES Once the bytes have been created, they are grouped into blocks for output by preceeding each block of 0 to 255 bytes with a character count byte. A block with a zero byte count terminates the Raster Data stream for a given image. These blocks are what are actually output for the Graphics Interchange Format (GIF) Page 14 Appendix C - Image Packaging & Compression GIF image. This block format has the side effect of allowing a decoding program the ability to read past the actual image data if necessary by reading block counts and then skipping over the data. Graphics Interchange Format (GIF) Page 15 Appendix D - Multiple Image Processing Since a GIF data stream can contain multiple images, it is necessary to describe processing and display of such a file. Because the image descriptor allows for placement of the image within the logical screen, it is possible to define a sequence of images that may each be a partial screen, but in total fill the entire screen. The guidelines for handling the multiple image situation are: 1. There is no pause between images. Each is processed immediately as seen by the decoder. 2. Each image explicitly overwrites any image already on the screen inside of its window. The only screen clears are at the beginning and end of the GIF image process. See discussion on the GIF terminator. "EA IFF 85" Standard for Interchange Format Files Document Date: January 14, 1985 From: Jerry Morrison, Electronic Arts Status of Standard: Released and in use 1. Introduction Standards are Good for Software Developers As home computer hardware evolves to better and better media machines, the demand increases for higher quality, more detailed data. Data development gets more expensive, requires more expertise and better tools, and has to be shared across projects. Think about several ports of a product on one CD-ROM with 500M Bytes of common data! Development tools need standard interchange file formats. Imagine scanning in images of "player" shapes, moving them to a paint program for editing, then incorporating them into a game. Or writing a theme song with a Macintosh score editor and incorporating it into an Amiga game. The data must at times be transformed, clipped, filled out, and moved across machine kinds. Media projects will depend on data transfer from graphic, music, sound effect, animation, and script tools. Standards are Good for Software Users Customers should be able to move their own data between independently developed software products. And they should be able to buy data libraries usable across many such products. The types of data objects to exchange are open-ended and include plain and formatted text, raster and structured graphics, fonts, music, sound effects, musical instrument descriptions, and animation. The problem with expedient file formats typically memory dumps is that they're too provincial. By designing data for one particular use (e.g. a screen snapshot), they preclude future expansion (would you like a full page picture? a multi-page document?). In neglecting the possibility that other programs might read their data, they fail to save contextual information (how many bit planes? what resolution?). Ignoring that other programs might create such files, they're intolerant of extra data (texture palette for a picture editor), missing data (no color map), or minor variations (smaller image). In practice, a filed representation should rarely mirror an in-memory representation. The former should be designed for longevity; the latter to optimize the manipulations of a particular program. The same filed data will be read into different memory formats by different programs. The IFF philosophy: "A little behind-the-scenes conversion when programs read and write files is far better than NxM explicit conversion utilities for highly specialized formats." So we need some standardization for data interchange among development tools and products. The more developers that adopt a standard, the better for all of us and our customers. Here is "EA IFF 1985" Here is our offering: Electronic Arts' IFF standard for Interchange File Format. The full name is "EA IFF 1985". Alternatives and justifications are included for certain choices. Public domain subroutine packages and utility programs are available to make it easy to write and use IFF-compatible programs. Part 1 introduces the standard. Part 2 presents its requirements and background. Parts 3, 4, and 5 define the primitive data types, FORMs, and LISTs, respectively, and how to define new high level types. Part 6 specifies the top level file structure. Appendix A is included for quick reference and Appendix B names the committee responsible for this standard. References American National Standard Additional Control Codes for Use with ASCII, ANSI standard 3.64-1979 for an 8-bit character set. See also ISO standard 2022 and ISO/DIS standard 6429.2. Amiga[tm] is a trademark of Commodore-Amiga, Inc. C, A Reference Manual, Samuel P. Harbison and Guy L. Steele Jr., Tartan Laboratories. Prentice-Hall, Englewood Cliffs, NJ, 1984. Compiler Construction, An Advanced Course, edited by F. L. Bauer and J. Eickel (Springer-Verlag, 1976). This book is one of many sources for information on recursive descent parsing. DIF Technical Specification (c)1981 by Software Arts, Inc. DIF[tm] is the format for spreadsheet data interchange developed by Software Arts, Inc. DIF[tm] is a trademark of Software Arts, Inc. Electronic Arts[tm] is a trademark of Electronic Arts. "FTXT" IFF Formatted Text, from Electronic Arts. IFF supplement document for a text format. Inside Macintosh (c) 1982, 1983, 1984, 1985 Apple Computer, Inc., a programmer's reference manual. Apple(R) is a trademark of Apple Computer, Inc. Macintosh[tm] is a trademark licensed to Apple Computer, Inc. "ILBM" IFF Interleaved Bitmap, from Electronic Arts. IFF supplement document for a raster image format. M68000 16/32-Bit Microprocessor Programmer's Reference Manual(c) 1984, 1982, 1980, 1979 by Motorola, Inc. PostScript Language Manual (c) 1984 Adobe Systems Incorporated. PostScript[tm] is a trademark of Adobe Systems, Inc. Times and Helvetica(R) are trademarks of Allied Corporation. InterScript: A Proposal for a Standard for the Interchange of Editable Documents (c)1984 Xerox Corporation. Introduction to InterScript (c) 1985 Xerox Corporation. 2. Background for Designers Part 2 is about the background, requirements, and goals for the standard. It's geared for people who want to design new types of IFF objects. People just interested in using the standard may wish to skip this part. What Do We Need? A standard should be long on prescription and short on overhead. It should give lots of rules for designing programs and data files for synergy. But neither the programs nor the files should cost too much more than the expedient variety. While we're looking to a future with CD-ROMs and perpendicular recording, the standard must work well on floppy disks. For program portability, simplicity, and efficiency, formats should be designed with more than one implementation style in mind. (In practice, pure stream I/O is adequate although random access makes it easier to write files.) It ought to be possible to read one of many objects in a file without scanning all the preceding data. Some programs need to read and play out their data in real time, so we need good compromises between generality and efficiency. As much as we need standards, they can't hold up product schedules. So we also need a kind of decentralized extensibility where any software developer can define and refine new object types without some "standards authority" in the loop. Developers must be able to extend existing formats in a forward- and backward-compatible way. A central repository for design information and example programs can help us take full advantage of the standard. For convenience, data formats should heed the restrictions of various processors and environments. E.g. word-alignment greatly helps 68000 access at insignificant cost to 8088 programs. Other goals include the ability to share common elements over a list of objects and the ability to construct composite objects containing other data objects with structural information like directories. And finally, "Simple things should be simple and complex things should be possible." Alan Kay. Think Ahead Let's think ahead and build programs that read and write files for each other and for programs yet to be designed. Build data formats to last for future computers so long as the overhead is acceptable. This extends the usefulness and life of today's programs and data. To maximize interconnectivity, the standard file structure and the specific object formats must all be general and extensible. Think ahead when designing an object. It should serve many purposes and allow many programs to store and read back all the information they need; even squeeze in custom data. Then a programmer can store the available data and is encouraged to include fixed contextual details. Recipient programs can read the needed parts, skip unrecognized stuff, default missing data, and use the stored context to help transform the data as needed. Scope IFF addresses these needs by defining a standard file structure, some initial data object types, ways to define new types, and rules for accessing these files. We can accomplish a great deal by writing programs according to this standard, but don't expect direct compatibility with existing software. We'll need conversion programs to bridge the gap from the old world. IFF is geared for computers that readily process information in 8-bit bytes. It assumes a "physical layer" of data storage and transmission that reliably maintains "files" as strings of 8-bit bytes. The standard treats a "file" as a container of data bytes and is independent of how to find a file and whether it has a byte count. This standard does not by itself implement a clipboard for cutting and pasting data between programs. A clipboard needs software to mediate access, to maintain a "contents version number" so programs can detect updates, and to manage the data in "virtual memory". Data Abstraction The basic problem is how to represent information in a way that's program-independent, compiler- independent, machine-independent, and device-independent. The computer science approach is "data abstraction", also known as "objects", "actors", and "abstract data types". A data abstraction has a "concrete representation" (its storage format), an "abstract representation" (its capabilities and uses), and access procedures that isolate all the calling software from the concrete representation. Only the access procedures touch the data storage. Hiding mutable details behind an interface is called "information hiding". What data abstraction does is abstract from details of implementing the object, namely the selected storage representation and algorithms for manipulating it. The power of this approach is modularity. By adjusting the access procedures we can extend and restructure the data without impacting the interface or its callers. Conversely, we can extend and restructure the interface and callers without making existing data obsolete. It's great for interchange! But we seem to need the opposite: fixed file formats for all programs to access. Actually, we could file data abstractions ("filed objects") by storing the data and access procedures together. We'd have to encode the access procedures in a standard machine-independent programming language la PostScript. Even still, the interface can't evolve freely since we can't update all copies of the access procedures. So we'll have to design our abstract representations for limited evolution and occasional revolution (conversion). In any case, today's microcomputers can't practically store data abstractions. They can do the next best thing: store arbitrary types of data in "data chunks", each with a type identifier and a length count. The type identifier is a reference by name to the access procedures (any local implementation). The length count enables storage-level object operations like "copy" and "skip to next" independent of object type. Chunk writing is straightforward. Chunk reading requires a trivial parser to scan each chunk and dispatch to the proper access/conversion procedure. Reading chunks nested inside other chunks requires recursion, but no lookahead or backup. That's the main idea of IFF. There are, of course, a few other detailsI Previous Work Where our needs are similar, we borrow from existing standards. Our basic need to move data between independently developed programs is similar to that addressed by the Apple Macintosh desk scrap or "clipboard" [Inside Macintosh chapter "Scrap Manager"]. The Scrap Manager works closely with the Resource Manager, a handy filer and swapper for data objects (text strings, dialog window templates, pictures, fontsI) including types yet to be designed [Inside Macintosh chapter "Resource Manager"]. The Resource Manager is a kin to Smalltalk's object swapper. We will probably write a Macintosh desk accessory that converts IFF files to and from the Macintosh clipboard for quick and easy interchange with programs like MacPaint and Resource Mover. Macintosh uses a simple and elegant scheme of 4-character "identifiers" to identify resource types, clipboard format types, file types, and file creator programs. Alternatives are unique ID numbers assigned by a central authority or by hierarchical authorities, unique ID numbers generated by algorithm, other fixed length character strings, and variable length strings. Character string identifiers double as readable signposts in data files and programs. The choice of 4 characters is a good tradeoff between storage space, fetch/compare/store time, and name space size. We'll honor Apple's designers by adopting this scheme. "PICT" is a good example of a standard structured graphics format (including raster images) and its many uses [Inside Macintosh chapter "QuickDraw"]. Macintosh provides QuickDraw routines in ROM to create, manipulate, and display PICTs. Any application can create a PICT by simply asking QuickDraw to record a sequence of drawing commands. Since it's just as easy to ask QuickDraw to render a PICT to a screen or a printer, it's very effective to pass them between programs, say from an illustrator to a word processor. An important feature is the ability to store "comments" in a PICT which QuickDraw will ignore. Actually, it passes them to your optional custom "comment handler". PostScript, Adobe's print file standard, is a more general way to represent any print image (which is a specification for putting marks on paper) [PostScript Language Manual]. In fact, PostScript is a full-fledged programming language. To interpret a PostScript program is to render a document on a raster output device. The language is defined in layers: a lexical layer of identifiers, constants, and operators; a layer of reverse polish semantics including scope rules and a way to define new subroutines; and a printing-specific layer of built-in identifiers and operators for rendering graphic images. It is clearly a powerful (Turing equivalent) image definition language. PICT and a subset of PostScript are candidates for structured graphics standards. A PostScript document can be printed on any raster output device (including a display) but cannot generally be edited. That's because the original flexibility and constraints have been discarded. Besides, a PostScript program may use arbitrary computation to supply parameters like placement and size to each operator. A QuickDraw PICT, in comparison, is a more restricted format of graphic primitives parameterized by constants. So a PICT can be edited at the level of the primitives, e.g. move or thicken a line. It cannot be edited at the higher level of, say, the bar chart data which generated the picture. PostScript has another limitation: Not all kinds of data amount to marks on paper. A musical instrument description is one example. PostScript is just not geared for such uses. "DIF" is another example of data being stored in a general format usable by future programs [DIF Technical Specification]. DIF is a format for spreadsheet data interchange. DIF and PostScript are both expressed in plain ASCII text files. This is very handy for printing, debugging, experimenting, and transmitting across modems. It can have substantial cost in compaction and read/write work, depending on use. We won't store IFF files this way but we could define an ASCII alternate representation with a converter program. InterScript is Xerox' standard for interchange of editable documents [Introduction to InterScript]. It approaches a harder problem: How to represent editable word processor documents that may contain formatted text, pictures, cross-references like figure numbers, and even highly specialized objects like mathematical equations? InterScript aims to define one standard representation for each kind of information. Each InterScript-compatible editor is supposed to preserve the objects it doesn't understand and even maintain nested cross-references. So a simple word processor would let you edit the text of a fancy document without discarding the equations or disrupting the equation numbers. Our task is similarly to store high level information and preserve as much content as practical while moving it between programs. But we need to span a larger universe of data types and cannot expect to centrally define them all. Fortunately, we don't need to make programs preserve information that they don't understand. And for better or worse, we don't have to tackle general-purpose cross-references yet. 3. Primitive Data Types Atomic components such as integers and characters that are interpretable directly by the CPU are specified in one format for all processors. We chose a format that's most convenient for the Motorola MC68000 processor [M68000 16/32-Bit Microprocessor Programmer's Reference Manual]. N.B.: Part 3 dictates the format for "primitive" data types where and only where used in the overall file structure and standard kinds of chunks (Cf. Chunks). The number of such occurrences will be small enough that the costs of conversion, storage, and management of processor- specific files would far exceed the costs of conversion during I/O by "foreign" programs. A particular data chunk may be specified with a different format for its internal primitive types or with processor- or environment- speci fic variants if necessary to optimize local usage. Since that hurts data interchange, it's not recommended. (Cf. Designing New Data Sections, in Part 4.) Alignment All data objects larger than a byte are aligned on even byte addresses relative to the start of the file. This may require padding. Pad bytes are to be written as zeros, but don't count on that when reading. This means that every odd-length "chunk" (see below) must be padded so that the next one will fall on an even boundary. Also, designers of structures to be stored in chunks should include pad fields where needed to align every field larger than a byte. Zeros should be stored in all the pad bytes. Justification: Even-alignment causes a little extra work for files that are used only on certain processors but allows 68000 programs to construct and scan the data in memory and do block I/O. You just add an occasional pad field to data structures that you're going to block read/write or else stream read/write an extra byte. And the same source code works on all processors. Unspecified alignment, on the other hand, would force 68000 programs to (dis)assemble word and long-word data one byte at a time. Pretty cumbersome in a high level language. And if you don't conditionally compile that out for other processors, you won't gain anything. Numbers Numeric types supported are two's complement binary integers in the format used by the MC68000 processor high byte first, high word first the reverse of 8088 and 6502 format. They could potentially include signed and unsigned 8, 16, and 32 bit integers but the standard only uses the following: UBYTE 8 bits unsigned WORD 16 bits signed UWORD 16 bits unsigned LONG 32 bits signed The actual type definitions depend on the CPU and the compiler. In this document, we'll express data type definitions in the C programming language. [See C, A Reference Manual.] In 68000 Lattice C: typedef unsigned char UBYTE; /* 8 bits unsigned */ typedef short WORD; /* 16 bits signed */ typedef unsigned short UWORD; /* 16 bits unsigned */ typedef long LONG; /* 32 bits signed */ Characters The following character set is assumed wherever characters are used, e.g. in text strings, IDs, and TEXT chunks (see below). Characters are encoded in 8-bit ASCII. Characters in the range NUL (hex 0) through DEL (hex 7F) are well defined by the 7-bit ASCII standard. IFF uses the graphic group RJS (SP, hex 20) through R~S (hex 7E). Most of the control character group hex 01 through hex 1F have no standard meaning in IFF. The control character LF (hex 0A) is defined as a "newline" character. It denotes an intentional line break, that is, a paragraph or line terminator. (There is no way to store an automatic line break. That is strictly a function of the margins in the environment the text is placed.) The control character ESC (hex 1B) is a reserved escape character under the rules of ANSI standard 3.64-1979 American National Standard Additional Control Codes for Use with ASCII, ISO standard 2022, and ISO/DIS standard 6429.2. Characters in the range hex 7F through hex FF are not globally defined in IFF. They are best left reserved for future standardization. But note that the FORM type FTXT (formatted text) defines the meaning of these characters within FTXT forms. In particular, character values hex 7F through hex 9F are control codes while characters hex A0 through hex FF are extended graphic characters like , as per the ISO and ANSI standards cited above. [See the supplementary document "FTXT" IFF Formatted Text.] Dates A "creation date" is defined as the date and time a stream of data bytes was created. (Some systems call this a "last modified date".) Editing some data changes its creation date. Moving the data between volumes or machines does not. The IFF standard date format will be one of those used in MS-DOS, Macintosh, or Amiga DOS (probably a 32-bit unsigned number of seconds since a reference point). Issue: Investigate these three. Type IDs A "type ID", "property name", "FORM type", or any other IFF identifier is a 32-bit value: the concatenation of four ASCII characters in the range R S (SP, hex 20) through R~S (hex 7E). Spaces (hex 20) should not precede printing characters; trailing spaces are ok. Control characters are forbidden. typedef CHAR ID[4]; IDs are compared using a simple 32-bit case-dependent equality test. Data section type IDs (aka FORM types) are restriced IDs. (Cf. Data Sections.) Since they may be stored in filename extensions (Cf. Single Purpose Files) lower case letters and punctuation marks are forbidden. Trailing spaces are ok. Carefully choose those four characters when you pick a new ID. Make them mnemonic so programmers can look at an interchange format file and figure out what kind of data it contains. The name space makes it possible for developers scattered around the globe to generate ID values with minimal collisions so long as they choose specific names like "MUS4" instead of general ones like "TYPE" and "FILE". EA will "register" new FORM type IDs and format descriptions as they're devised, but collisions will be improbable so there will be no pressure on this "clearinghouse" process. Appendix A has a list of currently defined IDs. Sometimes it's necessary to make data format changes that aren't backward compatible. Since IDs are used to denote data formats in IFF, new IDs are chosen to denote revised formats. Since programs won't read chunks whose IDs they don't recognize (see Chunks, below), the new IDs keep old programs from stumbling over new data. The conventional way to chose a "revision" ID is to increment the last character if it's a digit or else change the last character to a digit. E.g. first and second revisions of the ID "XY" would be "XY1" and "XY2". Revisions of "CMAP" would be "CMA1" and "CMA2". Chunks Chunks are the building blocks in the IFF structure. The form expressed as a C typedef is: typedef struct { ID ckID; LONG ckSize; /* sizeof(ckData) */ UBYTE ckData[/* ckSize */]; } Chunk; We can diagram an example chunk a "CMAP" chunk containing 12 data bytes like this: ---------------- ckID: | 'CMAP' | ckSize: | 12 | ckData: | 0, 0, 0, 32 | -------- | 0, 0, 64, 0 | 12 bytes | 0, 0, 64, 0 | --------- ---------------- The fixed header part means "Here's a type ckID chunk with ckSize bytes of data." The ckID identifies the format and purpose of the chunk. As a rule, a program must recognize ckID to interpret ckData. It should skip over all unrecognized chunks. The ckID also serves as a format version number as long as we pick new IDs to identify new formats of ckData (see above). The following ckIDs are universally reserved to identify chunks with particular IFF meanings: "LIST", "FORM", "PROP", "CAT ", and " ". The special ID " " (4 spaces) is a ckID for "filler" chunks, that is, chunks that fill space but have no meaningful contents. The IDs "LIS1" through "LIS9", "FOR1" through "FOR9", and "CAT1" through "CAT9" are reserved for future "version number" variations. All IFF-compatible software must account for these 23 chunk IDs. Appendix A has a list of predefined IDs. The ckSize is a logical block size how many data bytes are in ckData. If ckData is an odd number of bytes long, a 0 pad byte follows which is not included in ckSize. (Cf. Alignment.) A chunk's total physical size is ckSize rounded up to an even number plus the size of the header. So the smallest chunk is 8 bytes long with ckSize = 0. For the sake of following chunks, programs must respect every chunk's ckSize as a virtual end-of-file for reading its ckData even if that data is malformed, e.g. if nested contents are truncated. We can describe the syntax of a chunk as a regular expression with "#" representing the ckSize, i.e. the length of the following {braced} bytes. The "[0]" represents a sometimes needed pad byte. (The regular expressions in this document are collected in Appendix A along with an explanation of notation.) Chunk ::= ID #{ UBYTE* } [0] One chunk output technique is to stream write a chunk header, stream write the chunk contents, then random access back to the header to fill in the size. Another technique is to make a preliminary pass over the data to compute the size, then write it out all at once. Strings, String Chunks, and String Properties In a string of ASCII text, LF denotes a forced line break (paragraph or line terminator). Other control characters are not used. (Cf. Characters.) The ckID for a chunk that contains a string of plain, unformatted text is "TEXT". As a practical matter, a text string should probably not be longer than 32767 bytes. The standard allows up to 231 - 1 bytes. When used as a data property (see below), a text string chunk may be 0 to 255 characters long. Such a string is readily converted to a C string or a Pascal STRING[255]. The ckID of a property must be the property name, not "TEXT". When used as a part of a chunk or data property, restricted C string format is normally used. That means 0 to 255 characters followed by a NUL byte (ASCII value 0). Data Properties Data properties specify attributes for following (non-property) chunks. A data property essentially says "identifier = value", for example "XY = (10, 200)", telling something about following chunks. Properties may only appear inside data sections ("FORM" chunks, cf. Data Sections) and property sections ("PROP" chunks, cf. Group PROP). The form of a data property is a special case of Chunk. The ckID is a property name as well as a property type. The ckSize should be small since data properties are intended to be accumulated in RAM when reading a file. (256 bytes is a reasonable upper bound.) Syntactically: Property::= Chunk When designing a data object, use properties to describe context information like the size of an image, even if they don't vary in your program. Other programs will need this information. Think of property settings as assignments to variables in a programming language. Multiple assignments are redundant and local assignments temporarily override global assignments. The order of assignments doesn't matter as long as they precede the affected chunks. (Cf. LISTs, CATs, and Shared Properties.) Each object type (FORM type) is a local name space for property IDs. Think of a "CMAP" property in a "FORM ILBM" as the qualified ID "ILBM.CMAP". Property IDs specified when an object type is designed (and therefore known to all clients) are called "standard" while specialized ones added later are "nonstandard". Links Issue: A standard mechanism for "links" or "cross references" is very desirable for things like combining images and sounds into animations. Perhaps we'll define "link" chunks within FORMs that refer to other FORMs or to specific chunks within the same and other FORMs. This needs further work. EA IFF 1985 has no standard link mechanism. For now, it may suffice to read a list of, say, musical instruments, and then just refer to them within a musical score by index number. File References Issue: We may need a standard form for references to other files. A "file ref" could name a directory and a file in the same type of operating system as the ref's originator. Following the reference would expect the file to be on some mounted volume. In a network environment, a file ref could name a server, too. Issue: How can we express operating-system independent file refs? Issue: What about a means to reference a portion of another file? Would this be a "file ref" plus a reference to a "link" within the target file? 4. Data Sections The first thing we need of a file is to check: Does it contain IFF data and, if so, does it contain the kind of data we're looking for? So we come to the notion of a "data section". A "data section" or IFF "FORM" is one self-contained "data object" that might be stored in a file by itself. It is one high level data object such as a picture or a sound effect. The IFF structure "FORM" makes it self- identifying. It could be a composite object like a musical score with nested musical instrument descriptions. Group FORM A data section is a chunk with ckID "FORM" and this arrangement: FORM ::= "FORM" #{ FormType (LocalChunk | FORM | LIST | CAT)* } FormType::= ID LocalChunk ::= Property | Chunk The ID "FORM" is a syntactic keyword like "struct" in C. Think of a "struct ILBM" containing a field "CMAP". If you see "FORM" you'll know to expect a FORM type ID (the structure name, "ILBM" in this example) and a particular contents arrangement or "syntax" (local chunks, FORMs, LISTs, and CATs). (LISTs and CATs are discussed in part 5, below.) A "FORM ILBM", in particular, might contain a local chunk "CMAP", an "ILBM.CMAP" (to use a qualified name). So the chunk ID "FORM" indicates a data section. It implies that the chunk contains an ID and some number of nested chunks. In reading a FORM, like any other chunk, programs must respect its ckSize as a virtual end-of-file for reading its contents, even if they're truncated. The FormType (or FORM type) is a restricted ID that may not contain lower case letters or punctuation characters. (Cf. Type IDs. Cf. Single Purpose Files.) The type-specific information in a FORM is composed of its "local chunks": data properties and other chunks. Each FORM type is a local name space for local chunk IDs. So "CMAP" local chunks in other FORM types may be unrelated to "ILBM.CMAP". More than that, each FORM type defines semantic scope. If you know what a FORM ILBM is, you'll know what an ILBM.CMAP is. Local chunks defined when the FORM type is designed (and therefore known to all clients of this type) are called "standard" while specialized ones added later are "nonstandard". Among the local chunks, property chunks give settings for various details like text font while the other chunks supply the essential information. This distinction is not clear cut. A property setting cancelled by a later setting of the same property has effect only on data chunks in between. E.g. in the sequence: prop1 = x (propN = value)* prop1 = y where the propNs are not prop1, the setting prop1 = x has no effect. The following universal chunk IDs are reserved inside any FORM: "LIST", "FORM", "PROP", "CAT ", "JJJJ", "LIS1" through "LIS9", "FOR1" through "FOR9", and "CAT1" through "CAT9". (Cf. Chunks. Cf. Group LIST. Cf. Group PROP.) For clarity, these universal chunk names may not be FORM type IDs, either. Part 5, below, talks about grouping FORMs into LISTs and CATs. They let you group a bunch of FORMs but don't impose any particular meaning or constraints on the grouping. Read on. Composite FORMs A FORM chunk inside a FORM is a full-fledged data section. This means you can build a composite object like a multi-frame animation sequence from available picture FORMs and sound effect FORMs. You can insert additional chunks with information like frame rate and frame count. Using composite FORMs, you leverage on existing programs that create and edit the component FORMs. Those editors may even look into your composite object to copy out its type of component, although it'll be the rare program that's fancy enough to do that. Such editors are not allowed to replace their component objects within your composite object. That's because the IFF standard lets you specify consistency requirements for the composite FORM such as maintaining a count or a directory of the components. Only programs that are written to uphold the rules of your FORM type should create or modify such FORMs. Therefore, in designing a program that creates composite objects, you are strongly requested to provide a facility for your users to import and export the nested FORMs. Import and export could move the data through a clipboard or a file. Here are several existing FORM types and rules for defining new ones. FTXT An FTXT data section contains text with character formatting information like fonts and faces. It has no paragraph or document formatting information like margins and page headers. FORM FTXT is well matched to the text representation in Amiga's Intuition environment. See the supplemental document "FTXT" IFF Formatted Text. ILBM "ILBM" is an InterLeaved BitMap image with color map; a machine-independent format for raster images. FORM ILBM is the standard image file format for the Commodore-Amiga computer and is useful in other environments, too. See the supplemental document "ILBM" IFF Interleaved Bitmap. PICS The data chunk inside a "PICS" data section has ID "PICT" and holds a QuickDraw picture. Issue: Allow more than one PICT in a PICS? See Inside Macintosh chapter "QuickDraw" for details on PICTs and how to create and display them on the Macintosh computer. The only standard property for PICS is "XY", an optional property that indicates the position of the PICT relative to "the big picture". The contents of an XY is a QuickDraw Point. Note: PICT may be limited to Macintosh use, in which case there'll be another format for structured graphics in other environments. Other Macintosh Resource Types Some other Macintosh resource types could be adopted for use within IFF files; perhaps MWRT, ICN, ICN#, and STR#. Issue: Consider the candidates and reserve some more IDs. Designing New Data Sections Supplemental documents will define additional object types. A supplement needs to specify the object's purpose, its FORM type ID, the IDs and formats of standard local chunks, and rules for generating and interpreting the data. It's a good idea to supply typedefs and an example source program that accesses the new object. See "ILBM" IFF Interleaved Bitmap for a good example. Anyone can pick a new FORM type ID but should reserve it with Electronic Arts at their earliest convenience. [Issue: EA contact person? Hand this off to another organization?] While decentralized format definitions and extensions are possible in IFF, our preference is to get design consensus by committee, implement a program to read and write it, perhaps tune the format, and then publish the format with example code. Some organization should remain in charge of answering questions and coordinating extensions to the format. If it becomes necessary to revise the design of some data section, its FORM type ID will serve as a version number (Cf. Type IDs). E.g. a revised "VDEO" data section could be called "VDE1". But try to get by with compatible revisions within the existing FORM type. In a new FORM type, the rules for primitive data types and word-alignment (Cf. Primitive Data Types) may be overriden for the contents of its local chunks but not for the chunk structure itself if your documentation spells out the deviations. If machine-specific type variants are needed, e.g. to store vast numbers of integers in reverse bit order, then outline the conversion algorithm and indicate the variant inside each file, perhaps via different FORM types. Needless to say, variations should be minimized. In designing a FORM type, encapsulate all the data that other programs will need to interpret your files. E.g. a raster graphics image should specify the image size even if your program always uses 320 x 200 pixels x 3 bitplanes. Receiving programs are then empowered to append or clip the image rectangle, to add or drop bitplanes, etc. This enables a lot more compatibility. Separate the central data (like musical notes) from more specialized information (like note beams) so simpler programs can extract the central parts during read-in. Leave room for expansion so other programs can squeeze in new kinds of information (like lyrics). And remember to keep the property chunks manageably short let's say 2 256 bytes. When designing a data object, try to strike a good tradeoff between a super-general format and a highly-specialized one. Fit the details to at least one particular need, for example a raster image might as well store pixels in the current machine's scan order. But add the kind of generality that makes it usable with foreseeable hardware and software. E.g. use a whole byte for each red, green, and blue color value even if this year's computer has only 4-bit video DACs. Think ahead and help other programs so long as the overhead is acceptable. E.g. run compress a raster by scan line rather than as a unit so future programs can swap images by scan line to and from secondary storage. Try to design a general purpose "least common multiple" format that encompasses the needs of many programs without getting too complicated. Let's coalesce our uses around a few such formats widely separated in the vast design space. Two factors make this flexibility and simplicity practical. First, file storage space is getting very plentiful, so compaction is not a priority. Second, nearly any locally-performed data conversion work during file reading and writing will be cheap compared to the I/O time. It must be ok to copy a LIST or FORM or CAT intact, e.g. to incorporate it into a composite FORM. So any kind of internal references within a FORM must be relative references. They could be relative to the start of the containing FORM, relative from the referencing chunk, or a sequence number into a collection. With composite FORMs, you leverage on existing programs that create and edit the components. If you write a program that creates composite objects, please provide a facility for your users to import and export the nested FORMs. The import and export functions may move data through a separate file or a clipboard. Finally, don't forget to specify all implied rules in detail. 5. LISTs, CATs, and Shared Properties Data often needs to be grouped together like a list of icons. Sometimes a trick like arranging little images into a big raster works, but generally they'll need to be structured as a first class group. The objects "LIST" and "CAT" are IFF-universal mechanisms for this purpose. Property settings sometimes need to be shared over a list of similar objects. E.g. a list of icons may share one color map. LIST provides a means called "PROP" to do this. One purpose of a LIST is to define the scope of a PROP. A "CAT", on the other hand, is simply a concatenation of objects. Simpler programs may skip LISTs and PROPs altogether and just handle FORMs and CATs. All "fully-conforming" IFF programs also know about "CAT ", "LIST", and "PROP". Any program that reads a FORM inside a LIST must process shared PROPs to correctly interpret that FORM. Group CAT A CAT is just an untyped group of data objects. Structurally, a CAT is a chunk with chunk ID "CAT " containing a "contents type" ID followed by the nested objects. The ckSize of each contained chunk is essentially a relative pointer to the next one. CAT ::= "CAT " #{ ContentsType (FORM | LIST | CAT)* } ContentsType ::= ID -- a hint or an "abstract data type" ID In reading a CAT, like any other chunk, programs must respect it's ckSize as a virtual end-of-file for reading the nested objects even if they're malformed or truncated. The "contents type" following the CAT's ckSize indicates what kind of FORMs are inside. So a CAT of ILBMs would store "ILBM" there. It's just a hint. It may be used to store an "abstract data type". A CAT could just have blank contents ID ("JJJJ") if it contains more than one kind of FORM. CAT defines only the format of the group. The group's meaning is open to interpretation. This is like a list in LISP: the structure of cells is predefined but the meaning of the contents as, say, an association list depends on use. If you need a group with an enforced meaning (an "abstract data type" or Smalltalk "subclass"), some consistency constraints, or additional data chunks, use a composite FORM instead (Cf. Composite FORMs). Since a CAT just means a concatenation of objects, CATs are rarely nested. Programs should really merge CATs rather than nest them. Group LIST A LIST defines a group very much like CAT but it also gives a scope for PROPs (see below). And unlike CATs, LISTs should not be merged without understanding their contents. Structurally, a LIST is a chunk with ckID "LIST" containing a "contents type" ID, optional shared properties, and the nested contents (FORMs, LISTs, and CATs), in that order. The ckSize of each contained chunk is a relative pointer to the next one. A LIST is not an arbitrary linked list the cells are simply concatenated. LIST ::= "LIST" #{ ContentsType PROP* (FORM | LIST | CAT)* } ContentsType ::= ID Group PROP PROP chunks may appear in LISTs (not in FORMs or CATs). They supply shared properties for the FORMs in that LIST. This ability to elevate some property settings to shared status for a list of forms is useful for both indirection and compaction. E.g. a list of images with the same size and colors can share one "size" property and one "color map" property. Individual FORMs can override the shared settings. The contents of a PROP is like a FORM with no data chunks: PROP ::= "PROP" #{ FormType Property* } It means, "Here are the shared properties for FORM type <." A LIST may have at most one PROP of a FORM type, and all the PROPs must appear before any of the FORMs or nested LISTs and CATs. You can have subsequences of FORMs sharing properties by making each subsequence a LIST. Scoping: Think of property settings as variable bindings in nested blocks of a programming language. Where in C you could write: TEXT_FONT text_font = Courier; /* program's global default */ File(); { TEXT_FONT text_font = TimesRoman; /* shared setting */ { TEXT_FONT text_font = Helvetica; /* local setting */ Print("Hello ");/* uses font Helvetica */ } { Print("there.");/* uses font TimesRoman */ } } An IFF file could contain: LIST { PROP TEXT { FONT {TimesRoman} /* shared setting */ } FORM TEXT { FONT {Helvetica}/* local setting*/ CHRS {Hello } /* uses font Helvetica */ } FORM TEXT { CHRS {there.} /* uses font TimesRoman */ } } The shared property assignments selectively override the reader's global defaults, but only for FORMs within the group. A FORM's own property assignments selectively override the global and group-supplied values. So when reading an IFF file, keep property settings on a stack. They're designed to be small enough to hold in main memory. Shared properties are semantically equivalent to copying those properties into each of the nested FORMs right after their FORM type IDs. Properties for LIST Optional "properties for LIST" store the origin of the list's contents in a PROP chunk for the fake FORM type "LIST". They are the properties originating program "OPGM", processor family "OCPU", computer type "OCMP", computer serial number or network address "OSN ", and user name "UNAM". In our imperfect world, these could be called upon to distinguish between unintended variations of a data format or to work around bugs in particular originating/receiving program pairs. Issue: Specify the format of these properties. A creation date could also be stored in a property but let's ask that file creating, editing, and transporting programs maintain the correct date in the local file system. Programs that move files between machine types are expected to copy across the creation dates. 6. Standard File Structure File Structure Overview An IFF file is just a single chunk of type FORM, LIST, or CAT. Therefore an IFF file can be recognized by its first 4 bytes: "FORM", "LIST", or "CAT ". Any file contents after the chunk's end are to be ignored. Since an IFF file can be a group of objects, programs that read/write single objects can communicate to an extent with programs that read/write groups. You're encouraged to write programs that handle all the objects in a LIST or CAT. A graphics editor, for example, could process a list of pictures as a multiple page document, one page at a time. Programs should enforce IFF's syntactic rules when reading and writing files. This ensures robust data transfer. The public domain IFF reader/writer subroutine package does this for you. A utility program "IFFCheck" is available that scans an IFF file and checks it for conformance to IFF's syntactic rules. IFFCheck also prints an outline of the chunks in the file, showing the ckID and ckSize of each. This is quite handy when building IFF programs. Example programs are also available to show details of reading and writing IFF files. A merge program "IFFJoin" will be available that logically appends IFF files into a single CAT group. It "unwraps" each input file that is a CAT so that the combined file isn't nested CATs. If we need to revise the IFF standard, the three anchoring IDs will be used as "version numbers". That's why IDs "FOR1" through "FOR9", "LIS1" through "LIS9", and "CAT1" through "CAT9" are reserved. IFF formats are designed for reasonable performance with floppy disks. We achieve considerable simplicity in the formats and programs by relying on the host file system rather than defining universal grouping structures like directories for LIST contents. On huge storage systems, IFF files could be leaf nodes in a file structure like a B-tree. Let's hope the host file system implements that for us! Thre are two kinds of IFF files: single purpose files and scrap files. They differ in the interpretation of multiple data objects and in the file's external type. Single Purpose Files A single purpose IFF file is for normal "document" and "archive" storage. This is in contrast with "scrap files" (see below) and temporary backing storage (non-interchange files). The external file type (or filename extension, depending on the host file system) indicates the file's contents. It's generally the FORM type of the data contained, hence the restrictions on FORM type IDs. Programmers and users may pick an "intended use" type as the filename extension to make it easy to filter for the relevant files in a filename requestor. This is actually a "subclass" or "subtype" that conveniently separates files of the same FORM type that have different uses. Programs cannot demand conformity to its expected subtypes without overly restricting data interchange since they cannot know about the subtypes to be used by future programs that users will want to exchange data with. Issue: How to generate 3-letter MS-DOS extensions from 4-letter FORM type IDs? Most single purpose files will be a single FORM (perhaps a composite FORM like a musical score containing nested FORMs like musical instrument descriptions). If it's a LIST or a CAT, programs should skip over unrecognized objects to read the recognized ones or the first recognized one. Then a program that can read a single purpose file can read something out of a "scrap file", too. Scrap Files A "scrap file" is for maximum interconnectivity in getting data between programs; the core of a clipboard function. Scrap files may have type "IFF " or filename extension ".IFF". A scrap file is typically a CAT containing alternate representations of the same basic information. Include as many alternatives as you can readily generate. This redundancy improves interconnectivity in situations where we can't make all programs read and write super-general formats. [Inside Macintosh chapter "Scrap Manager".] E.g. a graphically- annotated musical score might be supplemented by a stripped down 4-voice melody and by a text (the lyrics). The originating program should write the alternate representations in order of "preference": most preferred (most comprehensive) type to least preferred (least comprehensive) type. A receiving program should either use the first appearing type that it understands or search for its own "preferred" type. A scrap file should have at most one alternative of any type. (A LIST of same type objects is ok as one of the alternatives.) But don't count on this when reading; ignore extra sections of a type. Then a program that reads scrap files can read something out of single purpose files. Rules for Reader Programs Here are some notes on building programs that read IFF files. If you use the standard IFF reader module "IFFR.C", many of these rules and details will be automatically handled. (See "Support Software" in Appendix A.) We recommend that you start from the example program "ShowILBM.C". You should also read up on recursive descent parsers. [See, for example, Compiler Construction, An Advanced Course.] % The standard is very flexible so many programs can exchange data. This implies a program has to scan the file and react to what's actually there in whatever order it appears. An IFF reader program is a parser. % For interchange to really work, programs must be willing to do some conversion during read-in. If the data isn't exactly what you expect, say, the raster is smaller than those created by your program, then adjust it. Similarly, your program could crop a large picture, add or drop bitplanes, and create/discard a mask plane. The program should give up gracefully on data that it can't convert. % If it doesn't start with "FORM", "LIST", or "CAT ", it's not an IFF-85 file. % For any chunk you encounter, you must recognize its type ID to understand its contents. % For any FORM chunk you encounter, you must recognize its FORM type ID to understand the contained "local chunks". Even if you don't recognize the FORM type, you can still scan it for nested FORMs, LISTs, and CATs of interest. % Don't forget to skip the pad byte after every odd-length chunk. % Chunk types LIST, FORM, PROP, and CAT are generic groups. They always contain a subtype ID followed by chunks. % Readers ought to handle a CAT of FORMs in a file. You may treat the FORMs like document pages to sequence through or just use the first FORM. % Simpler IFF readers completely skip LISTs. "Fully IFF-conforming" readers are those that handle LISTs, even if just to read the first FORM from a file. If you do look into a LIST, you must process shared properties (in PROP chunks) properly. The idea is to get the correct data or none at all. % The nicest readers are willing to look into unrecognized FORMs for nested FORM types that they do recognize. For example, a musical score may contain nested instrument descriptions and an animation file may contain still pictures. Note to programmers: Processing PROP chunks is not simple! You'll need some background in interpreters with stack frames. If this is foreign to you, build programs that read/write only one FORM per file. For the more intrepid programmers, the next paragraph summarizes how to process LISTs and PROPs. See the general IFF reader module "IFFR.C" and the example program "ShowILBM.C" for details. Allocate a stack frame for every LIST and FORM you encounter and initialize it by copying the stack frame of the parent LIST or FORM. At the top level, you'll need a stack frame initialized to your program's global defaults. While reading each LIST or FORM, store all encountered properties into the current stack frame. In the example ShowILBM, each stack frame has a place for a bitmap header property ILBM.BMHD and a color map property ILBM.CMAP. When you finally get to the ILBM's BODY chunk, use the property settings accumulated in the current stack frame. An alternate implementation would just remember PROPs encountered, forgetting each on reaching the end of its scope (the end of the containing LIST). When a FORM XXXX is encountered, scan the chunks in all remembered PROPs XXXX, in order, as if they appeared before the chunks actually in the FORM XXXX. This gets trickier if you read FORMs inside of FORMs. Rules for Writer Programs Here are some notes on building programs that write IFF files, which is much easier than reading them. If you use the standard IFF writer module "IFFW.C" (see "Support Software" in Appendix A), many of these rules and details will automatically be enforced. See the example program "Raw2ILBM.C". % An IFF file is a single FORM, LIST, or CAT chunk. % Any IFF-85 file must start with the 4 characters "FORM", "LIST", or "CAT ", followed by a LONG ckSize. There should be no data after the chunk end. % Chunk types LIST, FORM, PROP, and CAT are generic. They always contain a subtype ID followed by chunks. These three IDs are universally reserved, as are "LIS1" through "LIS9", "FOR1" through "FOR9", "CAT1" through "CAT9", and " ". % Don't forget to write a 0 pad byte after each odd-length chunk. % Four techniques for writing an IFF group: (1) build the data in a file mapped into virtual memory, (2) build the data in memory blocks and use block I/O, (3) stream write the data piecemeal and (don't forget!) random access back to set the group length count, and (4) make a preliminary pass to compute the length count then stream write the data. % Do not try to edit a file that you don't know how to create. Programs may look into a file and copy out nested FORMs of types that they recognize, but don't edit and replace the nested FORMs and don't add or remove them. That could make the containing structure inconsistent. You may write a new file containing items you copied (or copied and modified) from another IFF file, but don't copy structural parts you don't understand. % You must adhere to the syntax descriptions in Appendex A. E.g. PROPs may only appear inside LISTs. Appendix A. Reference Type Definitions The following C typedefs describe standard IFF structures. Declarations to use in practice will vary with the CPU and compiler. For example, 68000 Lattice C produces efficient comparison code if we define ID as a "LONG". A macro "MakeID" builds these IDs at compile time. /* Standard IFF types, expressed in 68000 Lattice C. */ typedef unsigned char UBYTE; /* 8 bits unsigned */ typedef short WORD; /* 16 bits signed */ typedef unsigned short UWORD; /* 16 bits unsigned */ typedef long LONG; /* 32 bits signed */ typedef char ID[4]; /* 4 chars in ' ' through '~' */ typedef struct { ID ckID; LONG ckSize; /* sizeof(ckData) */ UBYTE ckData[/* ckSize */]; } Chunk; /* ID typedef and builder for 68000 Lattice C. */ typedef LONG ID; /* 4 chars in ' ' through '~' */ #define MakeID(a,b,c,d) ( (a)<<<<24 | (b)<<<<16 | (c)<<<<8 | (d) ) /* Globally reserved IDs. */ #define ID_FORM MakeID('F','O','R','M') #define ID_LIST MakeID('L','I','S','T') #define ID_PROP MakeID('P','R','O','P') #define ID_CAT MakeID('C','A','T',' ') #define ID_FILLER MakeID(' ',' ',' ',' ') Syntax Definitions Here's a collection of the syntax definitions in this document. Chunk ::= ID #{ UBYTE* } [0] Property::= Chunk FORM ::= "FORM" #{ FormType (LocalChunk | FORM | LIST | CAT)* } FormType::= ID LocalChunk ::= Property | Chunk CAT ::= "CAT " #{ ContentsType (FORM | LIST | CAT)* } ContentsType ::= ID -- a hint or an "abstract data type" ID LIST ::= "LIST" #{ ContentsType PROP* (FORM | LIST | CAT)* } PROP ::= "PROP" #{ FormType Property* } In this extended regular expression notation, the token "#" represents a ckSize LONG count of the following {braced} data bytes. Literal items are shown in "quotes", [square bracketed items] are optional, and "*" means 0 or more instances. A sometimes-needed pad byte is shown as "[0]". Defined Chunk IDs This is a table of currently defined chunk IDs. We may also borrow some Macintosh IDs and data formats. Group chunk IDs FORM, LIST, PROP, CAT. Future revision group chunk IDs FOR1 I FOR9, LIS1 I LIS9, CAT1 I CAT9. FORM type IDs (The above group chunk IDs may not be used for FORM type IDs.) (Lower case letters and punctuation marks are forbidden in FORM type IDs.) 8SVX 8-bit sampled sound voice, ANBM animated bitmap, FNTR raster font, FNTV vector font, FTXT formatted text, GSCR general-use musical score, ILBM interleaved raster bitmap image, PDEF Deluxe Print page definition, PICS Macintosh picture, PLBM (obsolete), USCR Uhuru Sound Software musical score, UVOX Uhuru Sound Software Macintosh voice, SMUS simple musical score, VDEO Deluxe Video Construction Set video. Data chunk IDs "JJJJ", TEXT, PICT. PROP LIST property IDs OPGM, OCPU, OCMP, OSN, UNAM. Support Software These public domain C source programs are available for use in building IFF-compatible programs: IFF.H, IFFR.C, IFFW.C IFF reader and writer package. These modules handle many of the details of reliably reading and writing IFF files. IFFCheck.C This handy utility program scans an IFF file, checks that the contents are well formed, and prints an outline of the chunks. PACKER.H, Packer.C, UnPacker.C Run encoder and decoder used for ILBM files. ILBM.H, ILBMR.C, ILBMW.C Reader and writer support routines for raster image FORM ILBM. ILBMR calls IFFR and UnPacker. ILBMW calls IFFW and Packer. ShowILBM.C Example caller of IFFR and ILBMR modules. This Commodore-Amiga program reads and displays a FORM ILBM. Raw2ILBM.C Example ILBM writer program. As a demonstration, it reads a raw raster image file and writes the image as a FORM ILBM file. ILBM2Raw.C Example ILBM reader program. Reads a FORM ILBM file and writes it into a raw raster image. REMALLOC.H, Remalloc.c Memory allocation routines used in these examples. INTUALL.H generic "include almost everything" include-file with the sequence of includes correctly specified. READPICT.H, ReadPict.c given an ILBM file, read it into a bitmap and a color map PUTPICT.H, PutPict.c given a bitmap and a color map, save it as an ILBM file. GIO.H, Gio.c generic I/O speedup package. Attempts to speed disk I/O by buffering writes and reads. giocall.c sample call to gio. ilbmdump.c reads in ILBM file, prints out ascii representation for including in C files. bmprintc.c prints out a C-language representation of data for a bitmap. Example Diagrams Here's a box diagram for an example IFF file, a raster image FORM ILBM. This FORM contains a bitmap header property chunk BMHD, a color map property chunk CMAP, and a raster data chunk BODY. This particular raster is 320 x 200 pixels x 3 bit planes uncompressed. The "0" after the CMAP chunk represents a zero pad byte; included since the CMAP chunk has an odd length. The text to the right of the diagram shows the outline that would be printed by the IFFCheck utility program for this particular file. +-----------------------------------+ |'FORM' 24070 | FORM 24070 IBLM +-----------------------------------+ |'ILBM' | +-----------------------------------+ | +-------------------------------+ | | | 'BMHD' 20 | | .BMHD 20 | | 320, 200, 0, 0, 3, 0, 0, ... | | | + ------------------------------+ | | | 'CMAP' 21 | | .CMAP 21 | | 0, 0, 0; 32, 0, 0; 64,0,0; .. | | | +-------------------------------+ | | 0 | +-----------------------------------+ |'BODY' 24000 | .BODY 24000 |0, 0, 0, ... | +-----------------------------------+ This second diagram shows a LIST of two FORMs ILBM sharing a common BMHD property and a common CMAP property. Again, the text on the right is an outline a la IFFCheck. +-----------------------------------------+ |'LIST' 48114 | LIST 48114 AAAA +-----------------------------------------+ |'AAAA' | .PROP 62 ILBM | +-----------------------------------+ | | |'PROP' 62 | | | +-----------------------------------+ | | |'ILBM' | | | +-----------------------------------+ | | | +-------------------------------+ | | | | | 'BMHD' 20 | | | ..BMHD 20 | | | 320, 200, 0, 0, 3, 0, 0, ... | | | | | | ------------------------------+ | | | | | 'CMAP' 21 | | | ..CMAP 21 | | | 0, 0, 0; 32, 0, 0; 64,0,0; .. | | | | | +-------------------------------+ | | | | 0 | | | +-----------------------------------+ | | +-----------------------------------+ | | |'FORM' 24012 | | .FORM 24012 ILBM | +-----------------------------------+ | | |'ILBM' | | | +-----------------------------------+ | | | +-----------------------------+ | | | | |'BODY' 24000 | | | ..BODY 24000 | | |0, 0, 0, ... | | | | | +-----------------------------+ | | | +-----------------------------------+ | | +-----------------------------------+ | | |'FORM' 24012 | | .FORM 24012 ILBM | +-----------------------------------+ | | |'ILBM' | | | +-----------------------------------+ | | | +-----------------------------+ | | | | |'BODY' 24000 | | | ..BODY 24000 | | |0, 0, 0, ... | | | | | +-----------------------------+ | | | +-----------------------------------+ | +-----------------------------------------+ Appendix B. Standards Committee The following people contributed to the design of this IFF standard: Bob "Kodiak" Burns, Commodore-Amiga R. J. Mical, Commodore-Amiga Jerry Morrison, Electronic Arts Greg Riker, Electronic Arts Steve Shaw, Electronic Arts Barry Walsh, Commodore-Amiga Flic Files (.FLI) Format description: The details of a FLI file are moderately complex, but the idea behind it is simple: don't bother storing the parts of a frame that are the same as the last frame. Not only does this save space, but it's very quick. It's faster to leave a pixel alone than to set it. A FLI file has a 128-byte header followed by a sequence of frames. The first frame is compressed using a bytewise run-length compression scheme. Subsequent frames are stored as the difference from the previous frame. (Occasionally the first frame and/or subsequent frames are uncompressed.) There is one extra frame at the end of a FLI which contains the difference between the last frame and the first frame. The FLI header: byte size name meaning offset 0 4 size Length of file, for programs that want to read the FLI all at once if possible. 4 2 magic Set to hex AF11. Please use another value here if you change format (even to a different resolution) so Autodesk Animator won't crash trying to read it. 6 2 frames Number of frames in FLI. FLI files have a maxium length of 4000 frames. 8 2 width Screen width (320). 10 2 height Screen height (200). 12 2 depth Depth of a pixel (8). 14 2 flags Must be 0. 16 2 speed Number of video ticks between frames. 18 4 next Set to 0. 22 4 frit Set to 0. 26 102 expand All zeroes -- for future enhancement. Next are the frames, each of which has a header: byte size name meaning offset 0 4 size Bytes in this frame. Autodesk Animator demands that this be less than 64K. 4 2 magic Always hexadecimal F1FA 6 2 chunks Number of 'chunks' in frame. 8 8 expand Space for future enhancements. All zeros. After the frame header come the chunks that make up the frame. First comes a color chunk if the color map has changed from the last frame. Then comes a pixel chunk if the pixels have changed. If the frame is absolutely identical to the last frame there will be no chunks at all. A chunk itself has a header, followed by the data. The chunk header is: byte size name meaning offset 0 4 size Bytes in this chunk. 4 2 type Type of chunk (see below). There are currently five types of chunks you'll see in a FLI file. number name meaning 11 FLI_COLOR Compressed color map 12 FLI_LC Line compressed -- the most common type of compression for any but the first frame. Describes the pixel difference from the previous frame. 13 FLI_BLACK Set whole screen to color 0 (only occurs on the first frame). 15 FLI_BRUN Bytewise run-length compression -- first frame only 16 FLI_COPY Indicates uncompressed 64000 bytes soon to follow. For those times when compression just doesn't work! The compression schemes are all byte-oriented. If the compressed data ends up being an odd length a single pad byte is inserted so that the FLI_COPY's always start at an even address for faster DMA. FLI_COLOR Chunks The first word is the number of packets in this chunk. This is followed directly by the packets. The first byte of a packet says how many colors to skip. The next byte says how many colors to change. If this byte is zero it is interpreted to mean 256. Next follows 3 bytes for each color to change (one each for red, green and blue). FLI_LC Chunks This is the most common, and alas, most complex chunk. The first word (16 bits) is the number of lines starting from the top of the screen that are the same as the previous frame. (For example, if there is motion only on the bottom line of screen you'd have a 199 here.) The next word is the number of lines that do change. Next there is the data for the changing lines themselves. Each line is compressed individually; among other things this makes it much easier to play back the FLI at a reduced size. The first byte of a compressed line is the number of packets in this line. If the line is unchanged from the last frame this is zero. The format of an individual packet is: skip_count size_count data The skip count is a single byte. If more than 255 pixels are to be skipped it must be broken into 2 packets. The size count is also a byte. If it is positive, that many bytes of data follow and are to be copied to the screen. If it's negative a single byte follows, and is repeated -skip_count times. In the worst case a FLI_LC frame can be about 70K. If it comes out to be 60000 bytes or more Autodesk Animator decides compression isn't worthwhile and saves the frame as FLI_COPY. FLI_BLACK Chunks These are very simple. There is no data associated with them at all. In fact they are only generated for the first frame in Autodesk Animator after the user selects NEW under the FLIC menu. FLI_BRUN Chunks These are much like FLI_LC chunks without the skips. They start immediately with the data for the first line, and go line- by-line from there. The first byte contains the number of packets in that line. The format for a packet is: size_count data If size_count is positive the data consists of a single byte which is repeated size_count times. If size_count is negative there are -size_count bytes of data which are copied to the screen. In Autodesk Animator if the "compressed" data shows signs of exceeding 60000 bytes the frame is stored as FLI_COPY instead. FLI_COPY Chunks These are 64000 bytes of data for direct reading onto the screen. ----------------------------------------------------------------------- And here's the PRO extensions: ----------------------------------------------------------------------- This is supplemental info on the AutoDesk Animator FLI and FLC formats. The following is an attempt at describing the newer chunks and frames that are not described in the Turbo C FLI library documentation. Chunk type Chunk ID ---------- ----------- FLI_DELTA 7 (decimal) First WORD (16 bits) is the number of compressed lines to follow. Next is the data for the changing lines themselves, always starting with the first line. Each line is compressed individually. The first WORD (16 bits) of a compressed line is the number of packets in the line. If the number of packets is a negative skip -packets lines. If the number of packets is positive, decode the packets. The format of an individual packet is: skip_count size_count data The skip count is a single byte. If more than 255 pixels are to be skipped, it must be broken into 2 packets. The size_count is also a byte. If it is positive, that many WORDS of data follow and are to be copied to the screen. If it is negative, a single WORDS value follows, and is to be repeated -size_count times. Chunk type Chunk ID ---------- ----------- FLI_256_COLOR 4 (decimal) The first WORD is the number of packets in this chunk. This is followed directly by the packets. The first byte of a packet is how many colors to skip. The next byte is how many colors to change. If this number is 0, (zero), it means 256. Next follow 3 bytes for each color to change. (One each for red, green and blue). The only difference between a FLI_256_COLOR chunk (type 4 decimal) and a FLI_COLOR chunk (type 11 decimal) is that the values in the type 4 chunk range from 0 to 255, and the values in a type 11 chunk range from 0 to 63. NOTE: WORD refer to a 16 bit int in INTEL (Little Endian) format. WORDS refer to two-bytes (16 bits) of consecutive data. (Big Endian) .FLC special frames and chunks FLC's may contain all the above chunks plus one other: Chunk type Chunk ID ---------- ----------- FLI_MINI 18 (decimal) 12 (Hex) From what I understand, this is a miniture 64 x 32 version of the first frame in FLI_BRUN format, used as an button for selecting flc's from within Animator Pro. Simply do nothing with this chunk. FLC New Frame FLC's also contains a frame with the magic bytes set to hex 00A1. This is the first frame in the .flc file. Actually it isn't a frame at all but to have several chunks within it that specify file location info specific to Animator Pro. IE: filepath, font to use, and .COL file info. This FRAME may be skipped while loading. That's right! Ignore it! The frame header is the same length as all other frames. So you may read the frame header, then skip past the rest of the frame. NOTE: When reading the FLI header on the newer FLI and FLC files, the FLI signature bytes are AF12 instead of AF11 used in the older FLI files. Also, you cannot ignore the screen width and height they may not be 320 x 200. Allowable screen sizes include: 320 x 200, 640 x 480, 800 x 600, 1280 x 1024 NOTE: the delay value between frames appears to be in 1000th's of a second instead of 70th's. If you have any questions or more info on the FLI or FLC formats, please let me know. Mike Haaland CompuServe : 72300,1433 Delphi : MikeHaaland Internet : mike@htsmm1.las-vegas.nv.us Usenet : ...!htsmm1.las-vegas.nv.us!mike ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Programming the PC Speaker ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Written for the PC-GPE by Mark Feldman e-mail address : u914097@student.canberra.edu.au myndale@cairo.anu.edu.au ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ THIS FILE MAY NOT BE DISTRIBUTED ³ ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Disclaimer ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ I assume no responsibility whatsoever for any effect that this file, the information contained therein or the use thereof has on you, your sanity, computer, spouse, children, pets or anything else related to you or your existance. No warranty is provided nor implied with this information. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Basic Programming Info ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The PC speaker has two states, in and out (0 and 1, on and off, Adam and Eve etc). You can directly set the state of the PC speaker or you can hook the speaker up to the output of PIT timer 2 to get various effects. Port 61h controls how the speaker will operate as follows: Bit 0 Effect ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 0 The state of the speaker will follow bit 1 of port 61h 1 The speaker will be connected to PIT channel 2, bit 1 is used as switch ie 0 = not connected, 1 = connected. Playing around with the bits in port 61h can prevent the Borland BC++ and Pascal sound() procedures from working properly. When you are done using the speaker make sure you set bit's 0 and 1 of port 61h to 0. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Your First Tone ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Ok, so lets generate a simple tone. We'll send a string of 0's and 1's to the PC speaker to generate a square wave. Here's the Pascal routine: Uses Crt; const SPEAKER_PORT = $61; var portval : byte; begin portval := Port[SPEAKER_PORT] and $FC; while not KeyPressed do begin Port[SPEAKER_PORT] := portval or 2; Delay(5); Port[SPEAKER_PORT] := portval; Delay(5); end; ReadKey; end. On my 486SX33 this generates a tone of around about 100Hz. First this routine grabs the value from the speaker port, sets the lower two bits to 0 and stores it. The loop first sets the speaker to "on", waits a short while, sets it to "off" and waits another short while. I write the loop to do it in this order so that when a key is pressed and the program exits the loop the lower two bits in the speaker port will both be 0 so it won't prevent other programs which then use the speaker from working properly. This is a really bad way of generating a tone. While the program is running interrupts are continually occurring in the PC and this prevents the timing from being accurate. Try running the program and moving the mouse around. You can get a nicer tone by disabling interrupts first, but this would prevent the KeyPressed function from working. In any case we want to generate a nice tone of a given frequency, and using the Delay procedure doesn't really allow us to do this. To top it all off, this procedure uses all of the CPU's time so we can't do anything in the background while the tone is playing. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Using PIT Channel 2 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Connecting the PC speaker to PIT channel 2 is simply a matter of programming the channel to generate a square wave of a given frequency and then setting the lower two bits in the speaker port to a 1. Detailed information on programming the PIT chip can be found in the file PIT.TXT, but here is the pascal source you'll need to do the job: const SPEAKER_PORT = $61; PIT_CONTROL = $43; PIT_CHANNEL_2 = $42; PIT_FREQ = $1234DD; procedure Sound(frequency : word); var counter : word; begin { Program the PIT chip } counter := PIT_FREQ div frequency; Port[PIT_CONTROL] := $B6; Port[PIT_CHANNEL_2] := Lo(counter); Port[PIT_CHANNEL_2] := Hi(counter); { Connect the speaker to the PIT } Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3; end; procedure NoSound; begin Port[SPEAKER_PORT] := Port[SPEAKER_PORT] and $FC; end; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ Playing 8-bit Sound Through the PC Speaker ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Terminolgy ÄÄÄÄÄÄÄÄÄÄ To clear up any confusion, here's my own definition of some words I'll be using in this section: sample : A single value in the range 0-255 representing the input level of the microphone at any given moment. volume : A sort of generic version of sample, not limited to the 0-255 range. song : A bunch of samples in a row representing a continuous sound. string : A bunch of binary values (0-1) in a row. Programs like the legendary "Magic Mushroom" demo do a handly little trick to play 8-bit sound from the PC speaker by sending binary strings to the PC speaker for every sample they play. If the bits are all 0's, then the speaker will be "off". If they are all 1's the speaker will be "on". If they alternate 0's and 1's then the speaker will behave as if it's "half" on, and so forth. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Bit string Time speaker is on ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 11111111 100% ³ ³ 11101110 75% ³ ³ 10101010 50% ³ ³ 10001000 25% ³ ³ 00000000 0% ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Note that in this table I've used strings which are 8 bits long meaning that there can only be 9 discrete volume levels (since anywhere from 0 to 8 of them can be set to 1). In reality the strings would be longer. The problem with using bit strings such as this is getting accurate timing between each bit you send. One way around this is to put all the 1's at the front of the string and all the 0's at the end, like so: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Bit string Time speaker is on ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 11111111 100% ³ ³ 11111100 75% ³ ³ 11110000 50% ³ ³ 11000000 25% ³ ³ 00000000 0% ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This way you can send all the 1's as a single pulse and your timing doesn't have to be quite as accurate. The sound isn't quite as good, but I've found it to be pretty reasonable. A real advantage in using this method is that you can program the PIT chip for "interrupt on terminal count" mode, this mode is similar to the one-shot mode, but counting starts as soon as you load the PIT counter. So if you are playing an 11kHz song you simply load the PIT counter 11000 times a second with a value that's proportional to the sample value and trigger it. The speaker output will go low for the set time and then remain high until the next time you trigger it (in practise it doesn't matter whether the string of 1's make the speaker go "low" or "high", just so long as it's consistent). I've managed to get good results using PIT channel 2 to handle the one-shot for each sample and PIT channel 0 to handle when to trigger channel 2 (ie 11000 times a second). *PLUS* I was able to have a program drawing stuff on the screen while all this was going on in the background! Incidently I should mention here that the "interrupt on terminal count" mode does not generate an actual interrupt on the Intel CPU. The mode was given this name since the PIT can can be hooked up to a CPU to generate an interrupt. As far as I can tell IBM didn't do it like this. This technique does have one nasty side-effect though. If you are playing an 11kHz tone for example then the PC speaker will be being turned on and off exactly 11000 times a second, in other words you'll hear a nice 11kHz sine wave superimposed over the song (do any of you math weirdo's want to do a FFT to prove this for me?). A way around this is to play the song back at 22kHz and play each sample twice. This will result in a 22kHz sine wave which will pretty much be filtered out by the tiny PC speaker and the simple low-pass filter circuit that it's usually connected to on the motherboard. The PIT chip runs at a frequency of 1193181 Hz (1234DDh). If you are playing an 11kHz song at 22kHz then 1193181 / 22000 = 54 clocks per second, so you'll have to program the PIT to count a maximum of 54 clocks for each sample. What I'm getting at is that you'll only be able to play 54 discreet sample levels using this method, so you'll have to scale the 256 different levels in an 8-bit song to fit into this range which will also result in futher loss of sound quality. I sped things up considerably by pre- calculating a lookup table like so: var count_values : array[0..255] of byte; for level := 0 to 255 do count_values[level] := level * 54 div 255; Then for each sample I just look up what it's counter value is and send that to the PIT chip. Since each value is of byte size you can program the PIT chip to accept the LSB only (see PIT.TXT for more info). The following pascal code will set the PIT chip up for "interrupt on terminal count" mode where only the LSB of the count needs to be loaded: Port[PIT_CONTROL] := $90; Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3; And the following line will trigger the one-shot for a given sample value from 0-255: Port[PIT_CHANNEL_2] := count_values[sample_value]; Do that 22000 times a second and whaddaya know, you'll hear "8-bit" sound from your PC speaker! Here's a bit of code which works ok on my machine: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ const SPEAKER_PORT = $61; PIT_CONTROL = $43; PIT_CHANNEL_2 = $42; PIT_FREQ = $1234DD; DELAY_LENGTH = 100; procedure PlaySound(sound : PChar; length : word); var count_values : array[0..255] of byte; i, loop : word; begin { Set up the count table } for i := 0 to 255 do count_values[i] := i * 54 div 255; { Set up the PIT and connect the speaker to it } Port[PIT_CONTROL] := $90; Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3; { Play the sound } asm cli end; for i := 0 to length - 1 do begin Port[PIT_CHANNEL_2] := count_values[byte(sound^)]; for loop := 0 to DELAY_LENGTH do; Port[PIT_C