10

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;

  1. Page 0 is blank and visible.
  2. Page 1 is prepped with background.
  3. Calculate sprite positions.
  4. Store dirty rectangles (sprite areas) in off-screen vram.
  5. Draw sprites.
  6. Flip page.
  7. Copy entire visible page to non-visible page.
  8. On non visible page restore dirty rectangles (so we have a clean background).
  9. 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.

  • 4
    0.068 seconds per frame corresponds to 14.7 frames per second (1/0.068). I'd try removing the wait for vertical retrace as it will tend to round up your frame time to approximately the next highest integer multiple of 1/60th of a second (eg. 4/60 = 0.067). Note most games of this era didn't follow Michael Abrash's advice here, and used an off-screen buffer in system memory and copied it (with REP MOVSW or MOVSD) to on-screen video memory. On a video card like the Tseng ET4000 you should be able to get at least 3 Mb/s copying from system RAM to video RAM on a '386 PC. – Ross Ridge Jun 10 at 1:36
  • Thanks for that, I will do some tests and report back. I had thought about doing the system memory approach but figured that copying 76,800 bytes/pixels with only 19,200 physical copies would be faster (in 16 bit code) using the 4 byte latch approach. However, there seems to be some VGA penalties (wait states?) for each copy of the rep movsb performed. It could very well be that system memory copies don't have this... – Luke Jun 10 at 2:23
  • I remember vaguely that there were some issues with VGA RAM access timing which could be the reason the "penalty" you are seeing, but the details escape me, sorry. Definitely try system-to-VGA and VGA-to-system RAM copy, and see if it makes a difference. – dirkt Jun 10 at 5:29
  • 1
    This answer explains why using the VGA latches to do the copying may not be an advantage on 16-bit VGA cards: retrocomputing.stackexchange.com/a/8533 In particular reading video memory from a card like the ET4000 is going to be significantly slower than writing to video memory. – Ross Ridge Jun 10 at 6:36
  • @RossRidge I have done some tests and added an update to the question. Love to hear your thoughts. – Luke Jun 11 at 20:34
8

Your problem is mostly going to be rep movsb changing that to use rep movsw should help. If the VGA card is on a 8MHz (or 6MHz) original AT bus then the timings work like this:

3 cycles to read or write a byte
19,200 bytes being moved.
345,600 bus cycles per copy
8,000,000 / 345,600 is 23.14 copies per second.
6,000,000 / 345,600 is 17.36 copies per second.

Overall this tends to suggest your bus access might be at 6MHz.

I think I've understood correctly you're building the background off screen but using VGA memory so this means that your source access is also going over the bus so it's very likely your best timing will be something like this.

Going to movsw will improve this by a factor of two.

If you put your source in main memory then the times should be better as main memory access should be faster than three 8Mhz cycles.

I haven't seen ISA AT buses with a speed of faster than 12MHz as lots of cards stopped working at that speed (I used have a odd clone motherboard with this speed) so most bus accesses tended to use the AT 8MHz cycles (or maybe an 6MHz cycle)

Oh and finally don't forget the video card needs to access that memory as well (taking roughly a 1/20 of second[1] I think) so it will be asserting wait states while it reads the memory.

[1] memory access in total per frame is 320*240*4 307,200 bits/frame but the tseng is a 32 bit card so we get 9600 32 bit access/frame which means the total time spent accessing the 80ns memory would be 9600 *80E-9*60 which I work out to be 0.046 seconds but wait states mean this could be much higher.

  • 2
    Thanks for the information, some good detail in there. However, the VGA latch copy technique only works with bytes, so rep movsw or rep movsd are not available. For more info check out the Michael Abrash articles [1]. I will see what the system memory technique will do instead. Thanks! [1] phatcode.net/res/224/files/html/ch47/47-06.html#Heading6 – Luke Jun 10 at 9:21
  • Not being able to use rep mosvw is a shame. I'd guess some of the VGA era cards might have a builtin bitblt but it's probably very card specific so not useful in this case.. – PeterI Jun 10 at 10:59
  • I have done some tests and added an update to the question. Love to hear your thoughts. – Luke Jun 11 at 20:35
  • Working backwards from your numbers 8000*4 word copies means your doing 96,000 cycles over the AT bus a which if it's taking 68ms gives a bus speed of ~7MHz (which means main memory is probably the other 1MHz) – PeterI Jun 12 at 9:37
  • But if I copy from system memory to system memory then I don't experience the bottleneck, only when I either copy to/from a000:0000 or b000:0000. It seems that the bus can handle the load. I suspect that there are video related wait states of some sort, e.g. the video card is scanning the video segment and can therefore not be written to, or something like that. – Luke Jun 12 at 9:44
2

If the background is static it looks like you could skip doing the copying. Move step 7 outside the loop or skip it when the background has not changed. Keep a set of dirty rectangles for each frame. The flow for each frame would be:

  1. Save new dirty rectangles.
  2. Draw new Spites.
  3. Flip so this frame is displayed.
  4. When this frame is no longer displayed restore dirty rectangles.
  5. Goto 1.
New contributor
Brian is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
  • Thanks for that, but my background is not going to be static, that's why I need the frame copy. :) – Luke Jun 12 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.