VOGONS


VESA mode programming from real mode

Topic actions

First post, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

I have a hankering to write a full-screen games launcher for DOS, in the same manner as I have done for the NEC PC-98 and Sharp X68000.

I want to target 8086 and up, with 512kb VGA as a minimum (so that I can use 640x480 or 640x400 in 256 colours). So DJGPP and protected mode is out of the question.

I suspect using OpenWatcom (plain C) is probably the best toolset, if I'm not developing on the actual hardware, but what I'm not sure about yet is how to access the base VESA modes (probably 100h/101h) in real mode. Unchained 320x200 is easy, but without a linear framebuffer how do you go about writing to these modes?

Reply 1 of 77, by mpe

User metadata
Rank Oldbie
Rank
Oldbie

It's been a while but as I remember linear framebuffer was preferred mode to use SVGA modes, but optional. It was introduced in 2.0. You should be able to access pixels using banks mapping to 64K windows in real mode address space.

Blog|NexGen 586|S4

Reply 2 of 77, by Grzyb

User metadata
Rank Oldbie
Rank
Oldbie

Yes, it's perfectly possible to access SVGA frame buffer in real mode.
The video memory is in segment A000h, pixel format in 256-color modes is like in mode 13h, and the only difference is the need for bank-switching - the segment size is 64 KB, while the frame buffer in 640x480x8bpp is about 300 KB.

There may be, however, one big problem with running an SVGA game on a 8086-class machine: SPEED.

Reply 3 of 77, by Jo22

User metadata
Rank l33t++
Rank
l33t++
mpe wrote on 2021-01-14, 12:55:

It's been a while but as I remember linear framebuffer was preferred mode to use SVGA modes, but optional. It was introduced in 2.0. You should be able to access pixels using banks mapping to 64K windows in real mode address space.

Yes, I think so.

However, wasn't VESA VBE BIOS 1.x made to be called from Real-Mode and 2.x from Protected-Mode?

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 4 of 77, by mpe

User metadata
Rank Oldbie
Rank
Oldbie

VBE 2.0 is superset of 1.2. Yes, there is LFB and the new protected mode banking interface for improved performance, but 1.x functions accessed using int 10 are still there.

Chances are that if you target 8086 then you are unlikely to be dealing with 2.0 hardware anyway

Blog|NexGen 586|S4

Reply 5 of 77, by Grzyb

User metadata
Rank Oldbie
Rank
Oldbie
Jo22 wrote on 2021-01-14, 13:38:

However, wasn't VESA VBE BIOS 1.x made to be called from Real-Mode and 2.x from Protected-Mode?

No.
All VBE versions support calling INT 10h from real mode.
Protected mode is only necessary to access the linear frame buffer, as it's located far beyond 1 MB.
And the linear frame buffer is optional.

Reply 6 of 77, by Jo22

User metadata
Rank l33t++
Rank
l33t++
Grzyb wrote on 2021-01-14, 14:17:
No. All VBE versions support calling INT 10h from real mode. Protected mode is only necessary to access the linear frame buffer, […]
Show full quote
Jo22 wrote on 2021-01-14, 13:38:

However, wasn't VESA VBE BIOS 1.x made to be called from Real-Mode and 2.x from Protected-Mode?

No.
All VBE versions support calling INT 10h from real mode.
Protected mode is only necessary to access the linear frame buffer, as it's located far beyond 1 MB.
And the linear frame buffer is optional.

Ah, I see. Never mind. 😅
I've found online what I thought about.
VBE 2.x sorta got an optional PM interface it seems.

http://www.delorie.com/djgpp/doc/ug/graphics/vbe20.html

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 7 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Thanks for the pointers (ha!) folks. Just to clarify, this isn't for a game - it's a game browser/launcher interface, so speed isn't an outright priority. I've linked the two previous projects I wrote for a couple of Japanese systems, below:

Built for the NEC PC-98
https://www.youtube.com/watch?v=ItZVIFY-c10

Built for the Sharp X68000
https://www.youtube.com/watch?v=3cuFtyWimM8

The X68000 is neat, as the graphics ram is just all completely accessible directly - it's really, really nice; plus you get a lovely 16bit packed-pixel model (well, 15bit plus a 'brightness' bit). The PC-98, in PEGC hardware guise anyway, is far closer to the IBM PC/DOS; earlier machines have a bank switched video hardware, but the PEGC hardware and above has a VESA-like packed-pixel linear framebuffer so it's trivial to write to a buffer in memory and then flip it to the framebuffer as needed.

I can probably leverage most of what I wrote for the PC-98, since it's just a few minor DOS calls (it uses a slightly modified DJGPP library) that differ and the final copy from memory to the framebuffer (in 64kb chunks I see, in real-mode DOS). If I pick 640x400x256 as the display resolution, would it be a fair assumption that almost all VGA cards of 512kb and above that are >= VESA 1.0 compliant should be able to run it?

I'd like it to run on as wide a range of systems as possible, so trying to keep it <640KB with 512kb (Super) VGA hardware in real-mode only.

Reply 8 of 77, by Grzyb

User metadata
Rank Oldbie
Rank
Oldbie
megatron-uk wrote on 2021-01-14, 15:02:

If I pick 640x400x256 as the display resolution, would it be a fair assumption that almost all VGA cards of 512kb and above that are >= VESA 1.0 compliant should be able to run it?

Yes, even some of those early 256KB SVGA cards support 640x400x256.
However, such cards don't support VBE in their BIOSes, so some TSR would be necessary.

Reply 9 of 77, by Jo22

User metadata
Rank l33t++
Rank
l33t++
Grzyb wrote on 2021-01-14, 15:43:
megatron-uk wrote on 2021-01-14, 15:02:

If I pick 640x400x256 as the display resolution, would it be a fair assumption that almost all VGA cards of 512kb and above that are >= VESA 1.0 compliant should be able to run it?

Yes, even some of those early 256KB SVGA cards support 640x400x256.
However, such cards don't support VBE in their BIOSes, so some TSR would be necessary.

I second that, tried it myself a few times also on authentic hardware.
Early cards like the Paradise Professional VGA (PVGA1A/B/.. and WDC90Cxx) did include 640x400x256c drivers for popular programs of the day (AutoCAD, Publisher, Win 2.x, GEM)..

Here's a sample video of Win 2.x in 256c with emulated Paradise chipset.:
https://www.youtube.com/watch?v=-YGx-KeujIU

A set of working VBE TSRs can be found here, in case UniVBE just doesn't cut it:
Re: OAK OTI-037c - 800x600 mode ?

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 10 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Brilliant, that confirms my thoughts of using that mode as a baseline.

I suppose I could have targetted 320x200 which would work on *all* VGA cards, but using that mode to display box artwork, screenshots etc is just too much of a compromise for the "works on anything with VGA" advantage it comes with. It wouldn't be much of a game/art/launcher if every box art or screenshot was something silly like 80 pixels high!

Just got OpenWatcom installed and doing a first pass through of all of the non-hardware related support code I wrote for the PC-98. I've already picked up a few gotchas from the previous C99 code that was written for GCC/DJGPP such as my preprocessor directives for struct alignment not being valid in Watcom, as well as the (annoying) warning about lack of trailing return characters at the end of every file.

Reply 11 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

So I've gotten some basic stuff up and running to query the VESA BIOS, mainly the INT10 call to retrieve the basic VBE data structure and enumerate the mode list. So far, so good.

There are two functions so far:

vesa_getvbeinfo

int vesa_getvbeinfo(vbeinfo_t *vbeinfo){
// Retrieve basic VESA BIOS information

union REGS r;
struct SREGS s;

// Set VESA BIOS call parameters and store pointer
// to vbeinfo datastructure which will hold info
// after this call.
r.x.ax = VESA_BIOS_INFO;
r.x.di = FP_OFF(vbeinfo);
s.es = FP_SEG(vbeinfo);

// Call interrupt for VESA BIOS
int86x(0x10, &r, &r, &s);

if (r.x.ax != VESA_BIOS_SUCCESS){
// VESA BIOS call was not successful
if (VESA_VERBOSE){
printf("%s.%d\t Error, Unable to query VESA BIOS [return code %x]\n", __FILE__, __LINE__, r.x.ax);
}
return -1;
}

// Process VESA mode info structure data
if (VESA_VERBOSE){
printf("%s.%d\t VESA BIOS queried successfully!\n", __FILE__, __LINE__);
vesa_printvbeinfo(vbeinfo);
vesa_printvbemodes(vbeinfo);
}

return 0;
}

vesa_hasmode

void vesa_hasmode(int mode, vbeinfo_t *vbeinfo){
// Finds if a given VESA mode is present (and supported)
// by the current video adapter

int i;
int found;
unsigned short int *modes;
vesamodeinfo_t *modeinfo = NULL;
modeinfo = (vesamodeinfo_t *) malloc(sizeof(vesamodeinfo_t));

found = 0;
modes = (unsigned short int*) vbeinfo->mode_list_ptr;

// Find the mode in the list of modes
for (i = 0; modes[i] != VESA_MODELIST_LAST; i++){
if (modes[i] == mode){
found = 1;
}
}

if found){
vesa_getmodeinfo(mode, modeinfo);
if (modeinfo->ModeAttributes != 0){
// Mode present and available
return 0;
} else {
// Mode present, but unavailable (e.g too little RAM)
return -1;
}
} else {
// Mode not present
return -1;
}

}

I'm in the middle of writing the vesa_getmodeinfo function, but that will do the obvious and return the detailed mode data for a given mode, so that the application can determine whether 0x100h is available or not (for example). I suppose the graceful way to detect 640x400x256c support would be to enumerate the mode list for 0x100h first, and then if not found, try all the other modes listed and find another that matches the X and Y resolution and colour depth and allow the user to choose.

The two print routines are trivial, so I won't post them.

src/vesa.c.73	 VESA BIOS queried successfully!
src/vesa.c.84 VESA BIOS information follows
VBE Signature: VESA
VBE Vendor: S3 Incorporated. Trio64
VBE Version: 512
SW Version: 0
Vendor: 0
Product: 0
Version: 0
Total RAM: 2048KB
src/vesa.c.93 End of VESA BIOS information
----------
src/vesa.c.104 VESA mode list follows
Mode 0: 100h
Mode 1: 101h
Mode 2: 102h
Mode 3: 103h
Mode 4: 104h
Mode 5: 105h
Mode 6: 106h
Mode 7: 107h
Mode 8: 10dh
Mode 9: 10eh
Mode 10: 10fh
Mode 11: 110h
Mode 12: 111h
Mode 13: 112h
Mode 14: 113h
Mode 15: 114h
Mode 16: 115h
Mode 17: 116h
Mode 18: 117h
Mode 19: 150h
Mode 20: 151h
Mode 21: 152h
Mode 22: 153h
Mode 23: 160h
Mode 24: 161h
Mode 25: 162h
Mode 26: 165h
Mode 27: 170h
Mode 28: 171h
Mode 29: 172h
Mode 30: 175h
Mode 31: 190h
Mode 32: 191h
Mode 33: 192h
Mode 34: 207h
Mode 35: 209h
Mode 36: 20ah
Mode 37: 213h
Mode 38: 222h
Mode 39: 223h
Mode 40: 224h
Mode 41: 225h
src/vesa.c.113 Found 42 VESA modes

Reply 13 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Yes, I'm aware there isn't a free() call in that function. There also isn't in the outer graphics library that is calling these vesa functions in turn - but as of right now its only running the once and exiting.

Reply 14 of 77, by Benedikt

User metadata
Rank Member
Rank
Member

If you are going to use some sort of PutPixel function, buffer the current bank number somewhere and check whether you really have to update it.
A PutPixel function that explicitly sets the bank via VBE call every time is abysmally slow.

Reply 15 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

I'm using a double buffer, so I have a 256KB buffer in memory, write all of the updates to that, then flush it to VRAM in one operation (which will be in 64KB chunks compared to the X68000 and PC-98). There's no direct setting of pixels, and since there is no animation involved (only a scrollbar moving from one highlighted game to the next, or each games boxart or screenshot loading as it is selected) the latency of doing that once, say, every 500ms is probably going to be good enough. The rest of the application should easily fit in the other 300KB or thereabouts.

It's certainly not how you would do it for something reasonably fast like a game, or if you were memory constrained with a lot of logic, but keeping it this way means that I can keep almost all of the graphics routines (draw box, print bitmap font at this location, flash this text box etc) independent from the actual platform code (be it writing directory to memory mapped IO regions for the Sharp, a linear framebuffer on the NEC, or to paged 64KB video regions on PC/VGA).

I haven't got any graphical output running yet, but all of the utility code (scraping games from named directories, loading/saving config files, parsing keyboard input etc) is working, just like it did on the other platforms.

Reply 17 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Got my code querying the VBE, able to interrogate support for individual modes (I'm only interested in 100h for now) and able to set a new mode and return back to the original one.

I think I have one more function to implement, which is the flip of the local buffer to video memory, of course this will involve chunking the local 256KB buffer into 64KB windows in turn.

Once that's done I think most of the rest of the supporting graphics functions which are common for X68K and PC-98 should "just work".

Reply 18 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Trying to wrap my head around the VESA multiple-64KB window 'thing'.

I'm working from the following sources:

- http://www.pennelynn.com/Documents/CUJ/HTML/1 … EL/KREHBIEL.HTM
- http://www.monstersoft.com/tutorial1/VESA_intro.html#2.2
- https://web.archive.org/web/20090114055246/ht … nl/vesasp12.txt

Which are all more or less the same thing.

Thanks to my new-found understanding of some of the implicit type casting issues I have taken for granted all my C-programming life, I now have a working set of functions to set the VESA mode and calculate the size of the memory window, number of pixels per window, etc.

What I'm now trying to work out is exactly the relationship between the memory windows (4 as defined in 640x400x256c) and the on screen image.

I don't need to worry about placing individual pixels in the windows - I just need to copy an entire contiguous block of memory to all 4 windows.

I was working on something like this:

unsigned char	*vram;					
unsigned char vram_buffer[256000];
long int window_bytes;
unsigned char *VGA=(unsigned char *)0xA0000000L;

void gfx_Flip(){
// Copy a buffer of GFX_ROWS * GFX_COLS bytes to
// the active VRAM framebuffer for display.

unsigned short int window;

// Set the vram pointer to the start of the buffer
vram = vram_buffer;

// for each window in the number of windows for this video mode
for(window = 0; window < windows_in_use; window++ ){
vesa_SetWindow(window);
memcpy(&vga_segment, vram, window_bytes);
vram += window_bytes;
};

// Reset vram buffer pointer position
vram = vram_buffer;
}

I do have the actual segment address of the window, as returned from the VBE mode info, but I'm just hardcoding the VGA segment address for now (it appears that for 99% of cases they'll be the same?).

Should something like this work? Am I right to assume that each window in turn is sequential in relation to screen layout; so the first window will be the first 64kb worth of pixels, then the second window continues on directly from that pixel position, etc.

Reply 19 of 77, by megatron-uk

User metadata
Rank Oldbie
Rank
Oldbie

Getting somewhere....

progress1.png
Filename
progress1.png
File size
26.98 KiB
Views
401 views
File license
CC-BY-4.0

That should be a single bitmap loaded and placed in the middle of the screen (so at least one of them is correct 🤣!), along with the start of a progress bar and some bitmap font text horizontally centred just above the bottom. It's mostly working without modification from the NEC/Sharp versions.

It looks like I may have some calculations for the copy from my local screen buffer to the VESA windows off a little (hence the duplicated output), and I can see that my palette setting code isn't quite right (the colours on the font are reversed, and the progress bar should be a cyan-like shade of blue).

But, progress is progress.

gfx_Flip() currently looks like this:

void gfx_Flip(){
unsigned short int window;
vram = vram_buffer;
for(window = 0; window < windows_in_use; window++ ){
vesa_SetWindow(window);
_fmemcpy(VGA, vram, window_bytes - 1);
vram += window_bytes - 1;
};
vram = vram_buffer;
}