I'm doing some mode x programming and I noticed that my vram to vram copies are much slower than I expected. My copying method was described by Michael Abrash in his Graphics Programming Black Book and it's the technique were the VGA latches are used to do fast 4 pixel copying.
My set up is like this; I'm in 320x240 resolution and page 0 and 1 will be used to show the active scene. After showing a page, I will copy the entire page to the non-visible page. E.g;
- Page 0 is blank and visible.
- Page 1 is prepped with background.
- Calculate sprite positions.
- Store dirty rectangles (sprite areas) in off-screen vram.
- Draw sprites.
- Flip page.
- Copy entire visible page to non-visible page.
- On non visible page restore dirty rectangles (so we have a clean background).
- Back to 3.
However, at the moment I'm not even doing any sprite related effort. I.e. I'm only copying the visible page to the non-visible page and I get a frame rate of 14 fps. This is on a 386DX 40Mhz with various 16bit VGA cards [1], all the same behaviour. Also when I slow down to 16Mhz, I still get the 14fps which to me indicates to me so sort of bottleneck within the VGA itself.
When I'm not copying (basically do nothing) I get 60fps. With copying I was expecting to get at least 30fps. This is some of applicable VGA code:
void bitblt_begin()
{
// Set the bit mask to select all bits from the latches and
// none from the CPU, so that we can write the latch contents
// directly to memory - M. Abrash.
outpw(VGA_GRAPHIC_INDEX, 0x0008);
// Set the map mask to enable all planes.
outpw(VGA_SEQUENCER_INDEX, VGA_MAP_MASK + 0xff00);
}
void bitblt_end()
{
// Restore the bit mask to its default, which selects all bits
// from the CPU and none from the latches (the GC Index still
// points to Bit Mask) - M. Abrash
outpw(VGA_GRAPHIC_INDEX + 1, 0x00ff);
}
void bitblt_page(int source_page, int target_page)
{
unsigned short source_offset = _page_offsets[source_page];
unsigned short target_offset = _page_offsets[target_page];
_asm {
cld
// Init the source (ds:si) and target (es:di) offsets.
mov ax,0a000h
mov ds,ax
mov si,source_offset
mov es,ax
mov di,target_offset
mov cx,19200
rep movsb
}
}
void show_page(int page)
{
unsigned short offset;
unsigned short address_high, address_low;
offset = page_offsets[page];
address_high = VGA_ADDRESS_HIGH | (offset & 0xff00);
address_low = VGA_ADDRESS_LOW | (offset << 8);
while ((inp(VGA_INPUT_STATUS) & VGA_DISPLAY_ENABLE));
outpw(VGA_CRT_INDEX, address_high);
outpw(VGA_CRT_INDEX, address_low);
while (!(inp(VGA_INPUT_STATUS) & VGA_VRETRACE));
}
Within my program's main loop this is used as:
int page = 0;
while(true) {
// do keyboard checking here..
// Flip the page.
page ^= 1;
show_page(page);
// Calculate FPS.
// Copy the currently visible page to the non-visible page.
bitblt_begin();
bitblt_page(page, page ^ 1)
bitblt_end();
// Do sprite work (potentially).
// Write out simple status (i.e. FPS and some other timings, takes about 1ms to render).
}
So, when at 60 hz, 1 / 60 = 0.01666, or 16ms. If I can render my scene in under 16ms then I get 60 fps. But in my situation my scene render (the page copy) is about 68ms, which seems to correspond to the 14fps. I.e: 68 / 16 = 4.25, 60 / 4.25 = 14.1176.
Now, I'm not aiming for 60 fps, 30 would be just fine. But at the moment I getting 14 fps and I'm not even doing anything such as calculating and writing sprites.
What am I doing fundamentally wrong? Or can it simply not be done? Is 16 bit ISA just such a major bottle neck that this simply cannot be done?
UPDATE
So, I have done some more tests to see what happens when I copy from system memory to video memory and this is what I found out.
For simplicity I have switched to 320x200 mode x (70hz) since it will make copying less complicated since we're staying within the 64kb boundary, but the principle should be the same for 320x240 mode x.
I've created this bare bone copy routine. It's basically an unrolled loop that loops over each of the 4 planes and performs copy of 8000 words (a 4th of a screen) from system memory to video memory. At the moment it's just copying from 0000:0000
, which produces nothing of interest on the screen, just garbled noise, it's the copying that matters. For each rep movsw
I'm copying 8000
words, or 16000
bytes, i.e. a quarter of a 320x200 screen.
The routine below gives me 12 fps with a 68ms to perform the copy:
void copy_buffer(int page)
{
unsigned int target_offset = page_offsets[page];
_asm {
cld
mov ax,0x0000
mov ds,ax
mov ax,0xa000
mov es,ax
mov bx,target_offset
mov dx,VGA_SEQUENCER_INDEX
// Plane #0
mov ax,0x0102
out dx,ax
mov si,0
mov di,bx
mov cx,8000 // 320*200 / 4 / 2
rep movsw
// Plane #1
mov ax,0x0202
out dx,ax
mov si,0
mov di,bx
mov cx,8000 // 320*200 / 4 / 2
rep movsw
// Plane #2
mov ax,0x0402
out dx,ax
mov si,0
mov di,bx
mov cx,8000 // 320*200 / 4 / 2
rep movsw
// Plane #3
mov ax,0x0802
out dx,ax
mov si,0
mov di,bx
mov cx,8000 // 320*200 / 4 / 2
rep movsw
}
}
The findings...
Initially I thought, let's use b000:0000
as a source buffer, since that memory area definitely isn't use when in VGA graphics mode. However, this was running at 8 fps with the copy routine taking 132ms. When I switched the source buffer to 0000:0000
it nearly doubled performance!
Then I thought, OK, lets make the target buffer also in system memory. So I set ds:si
to 0000:0000
and es:di
to 8000:0000
(unlikely to be used by anything critical) and voila, 36fps were the copy routine takes only 18ms.
I also took out the out
instructions to see if that had any impact on performance and there was no noticeable difference. So, the 4 out
's to select each of the planes is not a bottleneck.
So, basically, touching any form of video memory in the a000:0000
and b000:0000
areas results in a severe performance loss.
If anyone has any ideas....?
UPDATE 2
I just did a test with plain mode 13h, copying 32000 words and I experience the same performance issues as in mode x when either copying to or from either segments a000:0000
or b000:0000
, i.e. 12fps. When I copy from system to system memory (e.g. from 0000:0000
to 8000:0000
) then I get 36fps.
[1] I have tested on multiple VGA cards, an ATI Wonder 16, Tseng ET4000AX, Western Digital, Chips & Tech and a few more.
rep movsb
performed. It could very well be that system memory copies don't have this...