|====================================| | | | TELEMACHOS proudly presents : | | | | Part 2 of the PXD trainers - | | | | EMS-HANDLING | | the way to do it | | | |====================================| ___---__--> The Peroxide Programming Tips <--__---___ <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> Intoduction ----------- Hiya... I'm Telemachos of Peroxide - a new danish group (yes... we DO have groups in Denmark too :))) ) In my last trainer "DOOM-walls : The technique and tricks" (which is really great, and I think you should leech it RIGHT away from the net :) ) I promised to do a trainer on the subject : Using the PC's memmory above the 640K limit which is set when using the standard memory-handling procedures provided with Turbo Pascal. Throughout this trainer I'll drop code in Pascal - with quite a load of asm code build in. But (as mentioned earlier.. uhmm.. like on line above this one) as most of the code is asm it should be easy to port to other languages like C/C++ or TASM (no kiddin' ?? :) ). So, this trainer won't teach you to do colorful graphic effects that'll impress your friends and make your girlfriend proud of you. It will perhaps also be a bit boring when I start talking about the PC's hardware-limitations and stuff... But I promise you - it'll be well worth the reading time anyway as it'll give you the key to fast computer graphic - the key to storing ALL your data in memory before the program executes :) So here we go.... "EMS-HANDLING : the way to do it :) " *************************** ATTENTION ARTISTS!!! ***************************** ARE YOU AN ARTIST ? DO YOU DRAW VGA-BITMAPS ? CAN YOU DO GFX IN ALL RESOLUTIONS - 320x200x256 AND VARIOUS SVGA-MODES ? DO YOU WANT TO SEE YOUR WORK IN AMAZING PRODUCTIONS ? CAN YOU DO GAME-GFX AS WELL AS BACKGROUNDS FOR DEMOS ? IF YOU MEET THE ABOVE TERMS (or some at least :) ), THEN DROP ME (TELEMACHOS) A MESSAGE OR MAIL THE GROUP AT : Peroxide@image.dk ****************************************************************************** If you want to get in contact with me, there are several ways of doing it : 1) Write me via FIDO-net : 2:235/350.22 2) E-mail me : tm@image.dk 3) Snail mail me : Kasper Fauerby Saloparken 226 8300 Odder Denmark 4) Call me (Voice ! ) : +45 86 54 07 60 Get this serie from the major demo-related FTP-sites or from our own homepage (soon at least ;) ) : http://www.image.dk/~peroxide or directly from my own : http://www.image.dk/~tm Extended vs. expanded memory ---------------------------- We all know that the memory above 1MB can be either Extended (XMS) or Expanded (EMS) memory - but why do we need theese two "different" kind of memory types ? Why can't it all just be MEM ??? The awnser lies all the way back when a guy called Bill Gates sat at the keyboard typing away on his main project - DOS! At that time 1MB of mem was a HUGE amount of mem.. so the guy never gave it a thought that perhaps someday even 1MB of mem would'nt be enough. He never thought that someday HE himself would release a OS that required 16MB of mem - just to run!! So.. DOS was made with a limitation of 1MB of mem. The first 640Kb being conventional memory - the rest being himem. But as time went on and people started to make up more and more complicated programs, using more and more mem, it soon became aparent that if someone did'nt came up with a way of supporting more memory, then DOS would die. So... eventually some clever clever guys came up with the XMS and EMS specifications - each with it's own advantages and disadvantages. The main reason why I have chosen to descripe the use of EMS memory is, that EMS-memory by far is the easiest himem to use. Its disadvantages (which I will descripe later) lies in the page-size, but with some clever codin' theese problems can be solved. What exactly is SEGMENTS and OFFSETS ? --------------------------------------- DOS in real-mode is a 16bit environment. This means that DOS uses the old 16bit registers originating from the good ol' 8086/8088 processor. Well, to be able to address every single byte of memory we have in the computer each byte has to have a unique address. If we use only one register (16bit) for such an address some quick calculations reveals that the largest address available would be : Largest address = 2^16 = 65536 Not very much ehh ?? Ie. using this model we can only address 64K of mem!!!! Even Bill Gates saw a problem in this and so he decided to use a few more bits to address the memory. Using 17 bits gives us a limit of 128K, 18 bits 256K, 19 bits 512Kb and finally the amount that Bill decided to use - namely 20 bits wich gives us a total of 1024 (1MB) of addressable memory. But how do you address a 20bit memory location using only 16bit registers ? Well.. the awnser lies in dividing the memory into MEMORY SEGMENTS. To address a specific address in such a segment a 16bit OFFSET is used to determine how far IN in the SEGMENT the information is placed. This makes each segment 64K big. Each SEGMENT also have a 16bit base address (fx. $a000 for the video card). When accessed the SEGMENT address is shifted 4 bits to the left - making it a 20bit value. The 16bit OFFSET address is now added to this 20bit address - the result being the final memory address. In theory a SEGMENT could begin at any given memory location, but as it's shifted 4 places to the left (multiplied by 16) all SEGMENT will begin at an address divisible by 16. To sum things up we can have 2^16 = 65536 different segments within the 1MB boundry set by the 20bit system. Lets take a look at the video card placed at $a000. This being a 16bit value we shift it 4 places to the left by multiplying it with 16. This gives us the memory location where the video card begin. $a000 shl 4 = $a0000 (hell... hex IS pretty neat huh ?? ) = 655360 bytes = 640K. WOW.. the card is JUST after the conventional memory :) So now you know what the memory from 640K to 704K does :) So every single byte of memory (below 1MB that is) is addressed through its SEGMENT and an OFFSET in this segment. As an example lets take a look at the VGA screen again. Its SEGMENT being $a000 all we need to know is the offset. In mode 13h the memory layout is lineary - ie. the first pixel being at offset 0, the second at offset 1 and so on. Lets say we want to plot the 256th pixel on screen (coordinates ( 256,0)). Then the offset will be 256 = $100 - so the address desciped in 16bit registers is pixel : $a000:$0100 Yeah right... I knew all that - but what about EMS? --------------------------------------------------- EMS is - as mentioned earlier - just a name for the physical memory above the 1MB boundry. Actually this mem is only *made* EMS mem because of the LIM-specifications. The LIM expanded memory specifications is a software interface between the expanded memory manager (EMM) and the software that wishes to use the EMS memory. LIM supports up to 32M of EMS memory. Because the computer (still talkin' real mode) can only physical address memory below 1M the EMS memory is handled through a WINDOW placed in himem - that is between 640K and 1024K. This window is called the page-frame. In the page-frame is several PHYSICAL EMS pages - each with the size of 16K - which I'll return to later. The EMS memory is divided into a number of LOGICAL pages... also with a size of 16K. The trick is to map these LOGICAL pages into the PHYSICAL pages... and then address them as normal memory. You can look at the EMS memory as a giant piece of paper with information written on it - but what you actually see of this information is determined by the position of the "windows" in the page frame. Take a look at this diagram : ----------------------------- up to 32MB | | | EMS memory : | | | | divided into lots of | | 16K blocks... LOGICAL | | pages! | | | | | |=============================| 1024K (1MB) / | / / / / / / / / / / / / / / | / |-----------------------------| 960K THIS IS / | THE PAGE FRAME : | HIMEM! / | | \ | divided into 12 16K | \ | physical memory blocks | \ |-----------------------------| 768K \ | / / / / / / / / / / / / / / | |=============================| 640K / | | / | 24 16K physical pages | / | intended only for | / | operating system / | THIS IS / | environment | CONVEN- \ |-----------------------------| 256K TIONAL \ | / / / / / / / / / / / / / / | MEMORY \ | / / / / / / / / / / / / / / | \ | / / / / / / / / / / / / / / | \ |=============================| 0K Besides the page frame, himem is stuffed with things like the video memory and all sorts of devices and drivers you load with the LoadHigh and DeviceHigh commands in your autoexec.bat and config.sys. Now, this is why you have so much less himem to play with when running EMS - the page frame swallows quite a bit of memory :) Ok... Now I understand EMS - How do I code it ??? -------------------------------------------------- Well.. the EMS functions are all controlled through int 67h. There are LOTS of functions, but I will only descripe the most important here. For a complete reference to all of the functions in int 67h get a copy of the file ems4spec.doc which is a complete transcription of the LIM-specifications. It is not included here with this tuturial as it's a 450Kb text file :) I think the easiest way to understand the different basic EMS functions is to go through an EMS unit I have created for this tuturial. Each procedure / function will be explained as we go through them. [ cut here for a rip of the unit TMEMS.PAS] Unit TMEMS; INTERFACE Var Handle : integer; {by declaring this a public variable I assume that you will only be allocating EMS pages ONCE during a program. It saves you from having to pass the handle to a couple of functions every time you call them. But then you can't fx. allocate 10 pages at the beginning of the program - and then allocate 15 more later. If you wan't to do that you'll have to rewrite a couple of the routines so that the correct handle must be passed to them when called.} Function Hex_String (Number: Integer): String; Function EMS_AreYouThere : Boolean; Procedure Pagestatus(Var Total, Available: Integer); Procedure Allocate_Pages(Needed: Integer); Procedure Map_Page(Logical : Integer;Physical : byte); Function Get_Frame_Address : Integer; Procedure Deallocate_Pages; Function Get_Version_Number : string; IMPLEMENTATION Function Hex_String (Number: Integer): string; Function Hex_Char (Number: Integer): Char; Begin If Number < 10 then Hex_Char := Char (Number + 48) else Hex_Char := Char (Number + 55); end; { Function Hex_char } Var S: string; Begin S := ''; S := Hex_Char ((Number shr 1) div 2048); Number := (((Number shr 1) mod 2048) shl 1) + (Number and 1); S := S + Hex_Char (Number div 256); Number := Number mod 256; S := S + Hex_Char (Number div 16); Number := Number mod 16; S := S + Hex_Char (Number); Hex_String := S + 'h'; end; { Function Hex_String } {uhm... this procedure is ripped from the file ems4spec.doc } {***********************} Function EMS_AreYouThere : Boolean; Var EMS_Name : string[8]; Returned_Name : string[8]; Position : integer; segm : word; Begin Returned_Name := ''; EMS_Name := 'EMMXXXX0'; {this is the ID string that SHOULD appear in the code segment of int 67h} asm mov ah,35h mov al,67h int 21h mov segm,es end; For Position := 0 to 7 do Returned_Name := Returned_Name + Chr (mem[segm:Position + $0A]); {This call will return the segment address where the ID string SHOULD be. If the ID string is there it'll be placed from offset $0A to $11} If Returned_Name = EMS_Name then EMS_AreYouThere := true else EMS_AreYouThere := false; end; { Function EMS_AreYouThere } {*******************} Procedure Pagestatus(Var Total, Available: Integer); Var HowManyInAll : word; HowManyAvailable : word; Begin asm mov ah,42h {this is EMS function nr. 42h } int 67h mov HowManyAvailable,bx mov HowManyInAll,dx end; Available:=HowManyAvailable; Total:=HowManyInAll; end; { Function Pagestatus } {This Procedure is nice when you want to know if there is enough free EMS memory to run your program.} {*************} Procedure Allocate_Pages(Needed: Integer); Assembler; asm mov ah,43h {this is EMS function nr. 43h} mov bx,[Needed] int 67h mov [handle],dx {NOT very nice... but heck... I like it this way} end; { Function Allocate_Pages } {When you run this procedure you allocate a certain amount of EMS pages. A handle is then assigned to these pages. Those of you who code TASM know of handles from file handling routines. But those of you who just use Pascal are'nt used to having a number assigned to a file or a piece of memory. Well.. its basicly the same thing as when you use the Assign comand in TP. Here you assign a string - namely the filename - to a var of the type : file (or file of bla bla bla). Later you use this var when you want to manipulate with the file. Same thing here - don't think about it.} {*****************} procedure Map_Page(Logical : Integer;Physical : byte); Assembler; asm mov ah,44h {EMS function nr. 44h} mov dx,[handle] {humm... NO comments :) } mov bx,[logical] mov al,[Physical] int 67h end; { Function Map_Page } {This procedure sets a window in the page frame to a certain logical page in EMS memory. From now on, when you manipulate with the physical page in the page frame you manipulate with the mapped logical page in EMS} {****************} Function Get_Frame_Address : Integer; Assembler; asm mov ah,41h {EMS function nr. 41h} int 67h mov ax,bx end; {This function returns the segment address of the page frame. Now this one is VERY!! important. When mapping logical pages into physical pages the programmer needs to know where to address the physical pages. Each physical page in the page frame has its own segement address. Physical page nr. 0 has THE SAME ADDRESS AS THE PAGE FRAME. From then on the physical pages are $400 (1Kb) apart - ie : (say that the page frame address is $D000 - it often is ) physical page nr. segment address 0 $D000 1 $D400 2 $D800 3 $DC00 4 $E000 } {*****************} Procedure Deallocate_Pages; Assembler; asm mov ah,45h mov dx,[handle] {humm... keeps popping up everywhere } int 67h end; { Procedure Deallocate_Pages } {This returns the allocated EMS pages to the memory pool. If you don't call this when closing a program down the EMS memory will be useless to all other programs too } {************} Function Get_Version_Number : String; Var Integer_Part, Fractional_Part: byte; Begin asm mov ah,46h int 67h cmp ah,0 jne @error mov bl,al shr bl,4 add bl,48 mov Integer_part,bl mov bl,al and bl,$F add bl,48 mov Fractional_Part,bl @error : end; Get_version_number:=chr(Integer_part)+'.'+chr(Fractional_part); end; { Function Get_Version_Number } {Ripped from ems4spec.doc - well.. the idea that is, rewritten to asm by ME } [ end unit code ] Writing a program that uses EMS memory --------------------------------------- OK.. when writing a program that uses EMS memory there are certain things that you must ALWAYS do. 1) Initialize the EMM manager. Call EMS_AreYouThere to see if EMS is available. 2) Check if all the pages needed are available in the current system. You'll have to try and calculate how many EMS pages your program is going to use. 3) Allocate these pages and (if you don't use my unit) store the EMS handle in some logically named variable.. like : "EMS_handle" :) 4) Get the address of the PAGE FRAME. Store this address in a variable - I personally likes to use the name FADDR (frame address). When ever you want to write to EMS memory you do the following : Mem[Faddr+Phys_PageNr * $400 : offset_in_page] := value_to_be_stored; Voila! Now you have written to the LOGICAL EMS page that was mapped in Phys_PageNr. Often You'll find that you only have to use Physical Page nr 0... If this is the case in your program simply use Mem[Faddr: offset_in_page] := Value_to_be_stored; In my Eye Of The Beholder demo from tuturial 1 I only use ONE physical page. 5) Go ahead and use the memory. When writing to EMS map the correct page into a physical page and write to this one - when reading from an EMS page map the page to the physical page and read from this one. It really IS as simple as this! 6) When you are through using the EMS memory don't forget to deallocate the used pages. Otherwise they will be unuseable by the system in any other applications. Advantages / Disadvantages --------------------------- The main advantage of using EMS memory is the ease of coding for it :) Really - with a few routines as those listed earlier in this tuturial you can code applications using up to 32MB of memory as easy as if it were all conventional memory. Play with the routines for an hour or so.... and you'll master EMS. Also EMS is fairly quick. Only an interrupt is required for mapping a LOGICAL EMS page into addressable memory. And with multiple physical pages available and some clever coding / structuring there should'nt be to many swaps in the program. Those of you who want to develop futher routines for handling EMS routines should take a look at the file ems4spec.doc - many more complicated functions are available through int 67h. Fx. I could mention that it IS possible to map multiple pages at the same time.... The main disadvantage of EMS memory is the page size. It really SUCK that an EMS pages originally was defined as 16K of memory. Why not 64K so that we could fit an entire segment into one page. As graphical programmers one of the first uses for EMS memory that we think of is storing graphic (well... I did anyway). Here a page size of 64K would be really cool as long as we stay in mode 13h. In that case we could store an entire screen in one EMS page... Well... unfortunately EMS pages is only 16K so we'll have to think of some other uses. One could split the screen up into 4 16K pieces... and then use 4 EMS pages pr. screen. But this is a little awkward as numerous interrupt calls must be made to map all four EMS pages needed to store the image. And when the image is to be used numerous physical_pages must be used - or the moving of data to the videocard must be split down into 4 parts.... one for each EMS page used. But fortunately lots of other datastructures fit into a 16K block of memory. Textures fx. is typically 64X64 or 128X128 pixels in size - making them take up 4K or 16K of memory. In my 3d-world each level supports up to 100 different textures - each of the size 128X128. It would be far to slow to load them from disk when they were to be used in runtime. But 100 textures of 16K of mem each... they take up 1.6MB of memory! Also each level supports up to 20 different monsters using 5 frames (128X128) each to animate movement - that's 100 16K blocks more! So all in all my game takes up 3.2MB of memory - I could'nt have done it without EMS memory. So - sprites in general, smaller bitmaps that does'nt take up the entire screen, lenses in demos, phongmaps, data structures for 3d objects - you name it. All those smaller structures often fit into an EMS page. When coding for EMS it is often required that you think about your structures when defining them. Perhaps you could split up some of your BIG records into several smaller ones ? Last remarks ------------- Well, that's about all for now. Hope you found this doc useful - and BTW : If you DO make anything public using these techniques please mention me in your greets or where ever you se fit. I DO love to see my name in a greeting :=) What to do next??? Humm... I think I'll do something a little more colorfull next time - otherwise I'll just scare of my readers :) One guy requested bitmap rotation - another would like some more 3d-stuff... But one of these days I'm planning on doing a doc on interrupts - and how to use them. Comments ?? If you have any good ideas for a subject you wish to see a tuturial on please mail me. If I like the idea (and know anything about it :) ) I'll write a tut on it. Humm... yeah - while I remember it. These docs will soon be available through my very own homepage (linked to our group homepage) so check out the following addresses : Peroxide Homepage : http://www.image.dk/~peroxide (this one is up by now) Telemachos' Page : http://www.image.dk/~tm (SHOULD be up when you read this :) ) Keep on coding and CuL8'er M8's Telemachos - April '97.