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.