From: pjfarley AT dorsai DOT org (Peter J. Farley III) Newsgroups: comp.os.msdos.djgpp Subject: Re: graphics programming tutorial Date: Wed, 17 Sep 1997 01:03:15 GMT Organization: None Lines: 471 Message-ID: <341f2be5.5393714@snews.zippo.com> References: <341e9b5c DOT 12935450 AT news DOT demon DOT co DOT uk> NNTP-Posting-Host: news.newsdawg.com To: djgpp AT delorie DOT com DJ-Gateway: from newsgroup comp.os.msdos.djgpp Precedence: bulk steven AT thurgood DOT demon DOT co DOT uk ( Steven Thurgood) wrote: > There is a link in the FAQ to a graphics programming tutorial. >Unfortunately the link seems dead, so could anybody please point me >towards a working link for this document? Or help me with my problem. >I am moving from msvc2, and having a bit of trouble. >Is there any docs giving info on making such a move? Or at least >something about programming in protected mode? >TIA Steve, This was posted to the newsgroup a while back, and I saved it. -----------------message-------------------------------------- I found this on some odd web page, its actually by a guy named ICBM, I'm taking no credit, just posting this to benefit the general public. It answers often asked questions about graphics in DJGPP. -- BEGIN -- This is to help all the people who are new to DJGPP. They are probably used to a 16 bit compiler, and real mode memory models. They have lots of problems and questions concerning their old graphics routines in DJGPP. Additionally, looking through the header files of DJGPP might help. The more relevant files are go32.h, dpmi.h, pc.h, sys/farptr.h, sys/nearptr.h and sys/movedata.h. --------------------------------------------------------------------------- VGA Mode 13h In real mode, one writes to the video memory starting at address $A000:0000 to draw graphics. However, DJGPP V2 programs cannot access this address near by default so one needs a selector spanning it. Luckily, we already have this selector. It is defined as "_dos_ds" in go32.h. Given a real mode segment:offset address (any DOS memory address) and _dos_ds, we could access the same address under protected mode! How exactly? In low level assembly, one would load a segment register with the selector's value and then copy the value, "segment*16+offset", into another register. Then just use this segment:register pair as a pointer. I will show how simple it is: short our_global_selector; ... our_global_selector = _dos_ds; ... movw _our_global_selector, %es movl $0xa0000, %edi ;** 0xA000*16 + 0x0000 = 0xA0000 Note: We are using AT&T syntax for assembly. Now we can use "es:edi" to write to video memory. How about a putpixel routine? movw _our_global_selector, %es movl $0xA0000, %edi movw _y, %ax imulw $320, %ax addw _x, %ax addw %ax, %di movb _color, %al stosb It is really straightforward once you get the hang of it. You could also use other register pairs: movw _our_global_selector, %fs movl $0xA0000, %ebx ... movb _color, %al movb %al, %fs:(%ebx) --------------------------------------------------------------------------- It even gets easier. There are many library functions you could use to write to the video memory. Let us start with the farptr hacks. #include #include #include #define putpixel(x,y,c) _farpokeb(_dos_ds, 0xA0000 + (y)*320 + (x), (c)) With optimizations turned on, DJGPP will inline the "_farpokeb" call. Passing the selector to every "_farp*" call can be slow and cumbersome. However, we can easily take care of this: /* circle routine */ ... _farsetsel(_dos_ds) /* loop */ ... _farnspokeb(0xA0000 + y*320 + x, color); ... /* end loop */ "_farsetsel" just preloads register "fs" with "_dos_ds" for succeeding "_farns*" calls. [GOTCHA!] ] This segment register is NOT guaranteed to contain the selector except immediately after the "_farsetsel" call! --------------------------------------------------------------------------- Let's examine the nearptr hacks. By the way, these functions turn off all memory protection. [GOTCHA!] ] Taken directly from the sys/nearptr.h file, "NO WARRANTY: WARNING, since these functions disable memory protection, they MAY DESTROY EVERYTHING ON YOUR COMPUTER!" #include #include #include unsigned char *videoptr = (unsigned char *)0xA0000; __djgpp_nearptr_enable(); videoptr[y*320 + x + __djgpp_conventional_base] = color; __djgpp_nearptr_disable(); Easy! [GOTCHA!] ] Just remember that "__djgpp_conventional_base" is NOT constant. It changes across memory allocation calls. --------------------------------------------------------------------------- There is yet another way to access video memory! #include #include #include unsigned char *videoptr = (unsigned char *)0xA0000; unsigned char *doublebuffer = (unsigned char *)malloc(320*200); void copy_buffer(void) { dosmemput(doublebuffer, 320*200, videoptr); } void copy_buffer2(void) { movedata(_my_ds(), doublebuffer, _dos_ds, videoptr, sizeof(*doublebuffer)); } These function calls are fairly obvious. By the way, "_my_ds()" just returns our code's selector; [GOTCHA!] ] do NOT confuse it with "_my_ds". One can find out more about these in the documentation and sys/movedata.h file. --------------------------------------------------------------------------- Other stuff that one might find helpful: #include #include #include void setmode(short mode) { __dpmi_regs r; r.x.ax = mode; __dpmi_int(0x10,&r); } struct rgbstruct { char red, green, blue; }; void setpalette(char color, struct rgbstruct rgb) { outportb(0x3c8, color); outportb(0x3c9, rgb.red); outportb(0x3c9, rgb.green); outportb(0x3c9, rgb.blue); } --------------------------------------------------------------------------- VBE 2.0 Again, I am not teaching VBE here. Get the SVGAKIT and VBE 2.0 docs from Scitech for reference. Now, let us start with the data structures: #define PACKED __attribute__ ((packed)) #pragma pack(1) struct VBE_vInfo { char VBESig[4] PACKED; short VBEVer PACKED; ... }; struct VBE_mInfo { short ModeAttrib PACKED; char WinAAttrib PACKED; ... unsigned int PhysBasePtr PACKED; ... }; #pragma pack() [GOTCHA!] ] The only surprising thing is probably the "PACKED" part. It just tells the compiler to align variables to bytes and fields to bits. --------------------------------------------------------------------------- Okay, let's write a VBE detect function. #include #include #include int VBE_detect(struct VBE_vInfo *vbeinfo) { __dpmi_regs r; assert(sizeof(*vbeinfo) < _go32_info_block.size_of_transfer_buffer); strncpy(vbeinfo->VBESig, "VBE2", 4); r.x.ax = 0x4F00; r.x.di = __tb & 0x0F; r.x.es = (__tb >> 4) & 0xFFFF; dosmemput(vbeinfo, sizeof(*vbeinfo), __tb); __dpmi_int(0x10, &r); dosmemget(__tb, sizeof(*vbeinfo), vbeinfo); ... } What did we just do? First, we know that the detect function "0x4F00" requires a memory buffer in the low 1M memory space (DOS memory) where it will return the mode tables, OEM strings, etc. So we would have to allocate space under 1M, equal to sizeof(struct VBE_vInfo). We could use "__dpmi_allocate_dos_memory()" but there is an easier way. DJGPP uses a global transfer buffer internally, usually 4K in size, and we can use this as our conventional memory buffer! After the call, we can just copy from this transfer buffer to our variable. Easy, isn't it? [GOTCHA!] ] One more little detail, this transfer buffer is defined as "_go32_info_block" or "__tb" in go32.h. Then "__tb & 0x0F" is just its real mode offset, and "(__tb >> 4) & 0xFFFF" is the segment. --------------------------------------------------------------------------- How about a VBE_getModeInfo function? void VBE_getModeInfo(unsigned short mode, struct VBE_mInfo *modeinfo) { __dpmi_regs r; assert(sizeof(*modeinfo) < _go32_info_block.size_of_transfer_buffer); r.x.ax = 0x4F01; r.x.cx = mode; r.x.di = __tb & 0x0F; r.x.es = (__tb >> 4) & 0xFFFF; __dpmi_int(0x10, &r); dosmemget(__tb, sizeof(*modeinfo), modeinfo); ... } --------------------------------------------------------------------------- Let's grab the linear video memory address in 640x480x8! struct VBE_mInfo modeinfo; __dpmi_meminfo mi; unsigned int linear_address; VBE_getModeInfo(0x101, &modeinfo); mi.size = (unsigned long)(modeinfo.XRes*modeinfo.YRes); mi.address = modeinfo.PhysBasePtr; __dpmi_physical_address_mapping(&mi); linear_address = mi.address; We have it! Using "__dpmi_physical_address_mapping()", we are able to convert the device's physical address to a linear address than we can use to poke around with, just like "0xA0000" with Mode 13h! However, before we start writing to video memory, we need to enable the video mode. r.x.ax = 0x4F02; r.x.bx = 0x4101; __dpmi_int(0x10, &r); --------------------------------------------------------------------------- And here is a nearptr putpixel hack! unsigned char *videoptr = (unsigned char *)linear_address; __djgpp_nearptr_enable(); videoptr[y*width + x +__djgpp_conventional_base] = color; __djgpp_nearptr_disable(); --------------------------------------------------------------------------- Farptr access is a bit more involved than the Mode 13h version. [GOTCHA!] ] We no longer have a selector handy to access this linear address. So what do we do? Easy, we make one! In addition, we set the base address of our new selector to the value of "linear_address"; this way, everything starts from offset 0. unsigned char *videoptr = (unsigned char *)0x0; short our_global_selector = __dpmi_allocate_ldt_descriptors(1); __dpmi_set_segment_base_address(our_global_selector, linear_address); _farpokeb(our_global_selector, videoptr + y*width +x, color); --------------------------------------------------------------------------- Our good movedata functions are still available. void copy_buffer2(void) { movedata(_my_ds(), doublebuffer, our_global_selector, videoptr, width*height); } --------------------------------------------------------------------------- One last thing, about the protected mode VBE protected mode interface. First, the relevant data structure, as described in SVGAKIT. #pragma pack(1) struct VBE_PMInterface { short pfsetWindow PACKED; short pfsetDisplayStart PACKED; short pfsetPalette PACKED; ... }; #pragma pack() This structure will store pointers to the VBE services, if one wants to call them directly from protected mode. Now let's write a function that will retrieve our function pointers. int VBE_getPMInterface(struct VBE_PMInterface *vbepmi) { __dpmi_regs r; r.x.ax = 0x4F0A; r.x.bx = 0x0000; __dpmi_int(0x10,&r); vbedpmi = (struct VBE_PMInterface *)malloc(sizeof(char)*r.x.cx); dosmemget(r.x.es*16 + r.x.di,sizeof(*vbepmi),vbepmi); } One allocates a buffer equal to size "r.x.cx" bytes after the "0x4F0A" call, then copies the protected mode interface information from DOS memory into it. Then the pointers to the VBE functions are easily retrieved: vbepmi + vbepmi->pfsetWindow vbepmi + vbepmi->pfsetDisplayStart vbepmi + vbepmi->pfsetPalette Please refer to the VBE 2.0 specifications for further information. By the way, do not forget to copy the interface from DOS memory after EVERY mode set, and free the buffer when shutting down the graphics system. --------------------------------------------------------------------------- VBE 1.2 There's not much to talk about here, really. I just want to show you the bank switching code, since we don't necessarily have the convenient linear frame buffer as before. void bankswitch(short bank) { __dpmi_regs r; r.x.ax = 0x4F05; r.x.bx = 0x0000; r.x.dx = bank; __dpmi_int(0x10, &r); /* In djasm: __asm__ __volatile__(" movw $0x4F05, %%ax; xorw %%bx, %%bx; int $0x10" : : "d" (bank) : "ax", "bx", "dx" ); */ } In VBE mode 101h (640x480x8), you can have each bank holding 64K (65536) bytes. So the bank is computed as: short bank = (short)((640*y + x) >> 16); To copy your double buffer to video memory using the nearptr functions, do: void copy_buffer(void) { char *source, *dest; source = doublebuffer; dest = videoptr + __djgpp_conventional_base; __djgpp_nearptr_enable(); /* 640*480*8bpp = 307200 bytes = 4*64K + 45056 bytes */ bankswitch(0); memcpy(dest, source, 65536L); bankswitch(1<>WinGran) != modeinfo.WinGranularity) WinGran++; -- END -- ---------------------------------------------------- Peter J. Farley III (pjfarley AT dorsai DOT org)