This page was last updated on March 13th, 2020(UTC) and it is currently May 30th, 2023(UTC).
That means this page is 3 years, 78 days, 2 hours, 48 minutes and 55 seconds old. Please keep that in mind.

39 - Getting Started with VGA Before we go any further, it might be nice if we could do some pretty graphics in DOS. So, here's what we're going to do: we're going to take a detour, for it will make what comes next seem a bit more relevant. Frist, the easy part.

#include <stdio.h> extern "C" int easymode(); extern "C" char buffer[0xfa00]; int main(){ return easymode(); } extern "C" int init(){ return 0; } extern "C" int cleanup(){ return 0; } extern "C" int draw(){ static int x; for(int i = 0; i < 0xfa00; i++) buffer[i] = i + x; printf("Meow\n"); x++; return 1; }

So, what we have here is main() return-calls easymode() which we'll see in a bit. init() is for startup code, and cleanup() is for cleaning up memory and things like that. draw() runs in a loop (hence why we need init() for starutp), constantly passing keypresses. It's running at whatever the DOS emulator's vertical refresh rate is set to. What you don't see yet is that a "keymap" of scancode is also exported from the assembly, along with all sorts of helper functions if you feel you really need. The video mode that gets set has 1 byte per pixel, with the colors being set by a palette that you can edit with the helper functions. Ctrl+C will exit the program, but if you return 0, it'll also exit, incase you want to throw your own in there. As you can see, printf() still works, which is not common. In most environments, you can't overlay like this. One issue that I'm having with this code, however, is that anything you type, it will pop back out of the program into the console. I have some code in there right now to try to clean up that issue, but it's not perfect, and it's also an extra function call to get input when we're not using it since it doesn't grab all the keys that we might want to use. I recommend that if you plan on making DOS stuff for more than just learning, you find a better solution that outright disables stdin entirely so it doesn't get reflected back to the command line.

.intel_syntax noprefix .code32 .global _settextmode .global _setvideomode .global _easymode .global _buffer .global _flip .global _setpalette .global _setbg .global _palette .global _nbinput .global _keymap .text /************************************************************************\ | int main() | \************************************************************************/ _easymode: #Enable special access to RAM call ___djgpp_nearptr_enable or eax, eax jnz 1f pushd 1 pushd offset err_msg call _fprintf add esp, 8 mov eax, 1 ret 1: mov eax, DWORD PTR ___djgpp_base_address sub [LFB], eax call _setvideomode #Hand it off to the program for init call _init or eax, eax jnz fail #Draw loop 1: call _flip call __conio_kbhit #not perfect, but it helps clean STDIN or eax, eax jz 2f call _getch #check for ctrl+c 2: mov al, [_keymap + 0x2e] or al, al jz 2f mov al, [_keymap + 0x1d] or al, al jnz 1f 2: call _draw or eax, eax jnz 1b #Cleanup and close 1: call _settextmode call ___djgpp_nearptr_disable call _cleanup ret fail: call _settextmode call ___djgpp_nearptr_disable mov eax, 1 ret dbs: .asciz "0x08x\n"; /************************************************************************\ | void settextmode() | \************************************************************************/ _settextmode: mov word ptr [_eax], 0x03 pushd offset reglist pushd 0x10 call ___dpmi_int add esp, 8 ret /************************************************************************\ | void setvideomode() | \************************************************************************/ _setvideomode: mov word ptr [_eax], 0x13 pushd offset reglist pushd 0x10 call ___dpmi_int add esp, 8 mov eax, [LFB] ret /************************************************************************\ | void flip() | \************************************************************************/ _flip: pushf push esi push edi mov dx, 0x3da 1: call _nbinput #only AL gets clobbered in al, dx #Wait until she wants to draw again and al, 0x08 jne 1b 1: call _nbinput #Better check here, too. in al, dx #Wait until she's done and al, 0x08 je 1b mov ecx, 0x3e80 mov esi, offset _buffer mov edi, [LFB] rep movsd mov edi, offset _buffer xor eax, eax mov ecx, 0x3e80 rep stosd pop edi pop esi popf ret /************************************************************************\ | setbg() | \************************************************************************/ _setbg: mov dx, 0x03c8 xor ax, ax out dx, al mov edx, 0x03c9 mov ax, [esp+4] out dx, al mov ax, [esp+8] out dx, al mov ax, [esp+12] out dx, al mov dx, 0x03c7 ret /************************************************************************\ | setpalette() | \************************************************************************/ _setpalette: push esi mov dx, 0x03c8 xor ax, ax out dx, al mov edx, 0x03c9 mov ecx, 0xc0 lea esi, [_palette] rep outsd dx, [esi] pop esi ret /************************************************************************\ | int nbinput() | \************************************************************************/ _nbinput: xor eax, eax in al, 0x60 test al, 0x80 jnz 1f mov byte ptr [_keymap + eax], 0xff ret 1: cmp al, 0x80 je 1f and al, 0x7f mov byte ptr [_keymap + eax], 0 1: ret /************************************************************************\ | DATA | \************************************************************************/ .data /************************************************************************\ | STRINGS | \************************************************************************/ err_msg: .asciz "Emulator did not allow direct access to memory." /************************************************************************\ | ARTIFICIAL REGISTER | \************************************************************************/ reglist: #We could save space using the stack, but this is more readable. _edi: .long 0 _esi: .long 0 _ebp: .long 0 _res: .long 0 _ebx: .long 0 _edx: .long 0 _ecx: .long 0 _eax: .long 0 _flags: .short 0 _es: .short 0 _ds: .short 0 _fs: .short 0 _gs: .short 0 _ip: .short 0 _cs: .short 0 _sp: .short 0 _ss: .short 0 /************************************************************************\ | VIDEO BUFFER | \************************************************************************/ LFB: .long 0xa0000 _buffer: .space 0xfa00 _palette: .space 0x300 _keymap: .space 0x80

This monstrosity takes up alot of space, even as a binary, because of all those heavy data structures. The good news is, the static linking still takes much more space. Alot of this is DOS specific code, so I wouldn't worry too much about what's going on here. I was going to set a higher graphics mode, but the tearing is already pretty bad despite the double buffer, and I was having a heck of a time figuring out all the DJGPP and DOS specific quirks that you normally don't deal with when coding an OS from scratch (also, when I realized editing the palettes didn't require ISRs, I concluded that it'd proably be fun to play with the palettes). Something worth noting is that I'm using a double buffer: it's this idea where you draw to a "holding place" when doing graphics processing, then once you're done with that "frame" you wait until the screen is done drawing (if it's drawing) then throwing the data from that holding place into video memory. Even here there are some limitations to it, but in the real world this stuff, anymore, is usually done by the GPU, not the CPU. Take note of the functions available, as i'm going to be using them next round.

Get your own web kitty here!
©Copyright 2010-2023. All rights reserved.