Horizontal Retrace Trainer

If you write any programs using this information, please credit me somewhere, and send me a copy if you feel generous.

Disclaimer

Please ensure you see the site information for legal information before progressing.

Assumptions I am Making

If you don't know either or both of these things, scour the Internet for more information. It is already swamped with good trainers for these subjects. Highly Recommended: Denthor's VGA Trainers. They start VGA programming from scratch. I learnt from them. Very Good. Plus, the later ones give you fast assembler code to use in your programs.

Also recommended: the PC Games Programmer's Encyclopaedia (PCGPE).

Introduction

No, you haven't read the title wrong, you can write programs that will display more than 256 physical colours at the same time on a VGA. What's more, you don't have to reduce the resolution from the standard modes. You can write using as little as 64k of video memory, because separate video pages are not necessary. (though most VGAs have at least 256k). There are no tricks like flicking back and forth very fast between two pictures, or photographically enhancing the picture. You will see individual pixels with over 256 different physical colours.

It's all down to a clever trick called the horizontal retrace. However, there are a few drawbacks. First, speed is of prime importance. Although you can draw simple pictures using a slow PC (I run most of my code on a 386sx/16!), the faster your PC, the more colours you will be able to show at the same time. Also, you need to be continually 'helping' the VGA to redraw the picture the way you want it, so forget about playing digitised music in the background (or doing anything else, come to that) unless you have a Cray handy.

Anyway, enough babble. Stride forth into the information!

Background

You may know about the vertical retrace already. Anyway, if you don't, here's an explanation:

There is a gun at the back of your screen, which fires pixels at the glass at the front of your screen. The gun fires pixels starting from the top left corner, and travelling to the top right. It then moves down and left to the start of the next line, draws that line, and so on until it reaches the bottom. Then it moves up to the top left hand corner and starts again. This complete screen redraw occurs 60-70 times a second, depending on your monitor and/or video card. The vertical retrace is the (short amount of) time when the gun is not firing pixels because it is moving from the bottom right corner to the top left. Considering that the screen is redrawn about 60-70 times a second, you can understand how short this time is. Nevertheless, processors are very fast, and you can easily change a few palette values or add some pixels in this time. In fact, a well-known trick is to change palette values during the vertical retrace, cutting out the 'snow' effect that occurs when changing the palette on some screens.

The horizontal retrace is the (even shorter) amount of time when the gun is moving from the end of one line to the start of the next. This time, as you can imagine, is very short. But things can still be done. And one of the things you can do is change palette values.

Note: I will assume that you are using a 256-attribute mode, though this technique applies to any VGA mode.

If, for example, you filled the screen with pixels, all with the same standard VGA attribute, say attribute 1, you could then wait until the gun finished writing each line, and then change the palette value, so that the gun drew a different colour (although the same VGA attribute) on the next line, and then again at the end of that line, and so on. You then wait for the vertical retrace and then do the same thing all over again. Of course you still can't have more than 256 attributes on each line (though you would need a very fast PC to change over 256 in that time anyway). Now you can see why you need to be continuously 'helping' the VGA redraw the screen! In theory, therefore, you can have (number of attributes * number of lines) different physical colours on the screen, with a maximum of 256 on each line (assuming you are using a 256-attribute mode).

Detailed Information

Here is the pseudo-code for a program which fills part of the screen with a red gradient, dark at the top, bright at the bottom, using only one attribute in the process:

    Select your favourite VGA mode (any one will do)
    Draw Every Pixel With Attribute 1

    Loop Continuously (or until a key is pressed)
    {
        WaitFor_VerticalRetrace_ToStart (to make sure we start at the top
                                             of the screen).

        Loop From X = Dark Red to X = Bright Red
        {
            WaitFor_HorizontalRetrace_ToStart
            Set Attribute 1 to X
        }
    }

A few comments on this code:

When to finish the inner loop can be a difficult thing to decide. You may have to experiment, because if you want to change more than one attribute inside the loop, you may end up finding, if your computer is slow, you cannot change everything you want before the next h-retrace starts. If the problem occurs, you may get behind, and since you cannot ask the VGA which line it is currently drawing (to my knowledge - please tell me if you know otherwise), you may think that the VGA is currently drawing a different line to the one which you think it is drawing - obviously a messy situation.

You, of course, can write most of the above code in your favourite language (I personally use C++ with in-line assembler, but it's up to you). It must be fast, however. But first you need to know how to detect Vertical and Horizontal Retraces:

    Input from port 03DA Hexadecimal as a byte.
    If Bit 4 (1-based) is NOT set, a vertical retrace is in progress.
    If Bit 1 (1-based) is NOT set, a horizontal retrace is in progress.

The recommendation from me is to enter a function which waits for a retrace, loop around while the bit = 0 (in the retrace), loop while the bit = 1 (not in the retrace), and then, when it becomes 0 again, do your funny stuff. This is because otherwise, if you just enter your function and loop until it equals 0, you may catch the appropriate retrace as it is nearing its end, and so your subsequent code may overrun onto screen redrawing.

Here is the assembler code for a vertical retrace:

    mov dx, 3dah ; Load the port
l1:
    in al, dx    ; Input a byte
    and al, 08h  ; 'And' it with a set bit 4.
    jnz l1       ; Continue looping while bit 4 = 0 (i.e. in retrace)
l2:
    in al, dx    ; Input a byte.
    and al, 08h  ; 'And' it with a set bit 4.
    jz l2        ; Continue looping while bit 4 = 1 (i.e. not in retrace)

For a horizontal retrace, just replace the 08h with 01h (i.e. bit 1 instead of bit 4).

However, if you run the code so far, you will notice that it flickers a lot. The reasons that it flickers is because interrupts are being generated. Every time you move your mouse, or press a key on the keyboard, or one of a million and one other things that generate an interrupt (most of which aren't even caused by you), the processor is interrupted (which is why they are called interrupts, strangely enough). So to (almost) eliminate flicker, just disable the interrupts!

In Assembler, enabling and disabling can be done with two instructions:

   CLI      Clear Interrupt Flag (Disable Interrupts)
   STI      Set   Interrupt Flag (Enable  Interrupts)

In other languages, either write assembler code in-line, or find some function that will do it for you (some run-time libraries will have one). Always make sure you enable interrupts before your program ends, otherwise the system may lock up.

However, there are a few problems with disabling interrupts. Firstly, be careful what you do. You cannot always call an interrupt (though this varies) and although this is usually clear to people programming in pure assembler, if you are using C/Pascal or some other mixed-level language, it may not be clear when you are calling an interrupt. However, if you are merely manipulating variables, or performing arithmetic, you can be fairly sure that you are not calling interrupts.

There is another important problem. The standard VGA method of changing palette registers, is, of course, via interrupts. This may cause a few problems! However, there is another method. Put the attribute you want to change out to port 03C8 hexadecimal, and then put the red value (0 - 63 in most modes) out to the port above that, then the green to the one above that, then finally the blue to the one above that. Here is the in-line assembler code for C:

   mov dx, 3c8h
   mov al, [attribute]
   out dx, al
   inc dx
   mov al, [red]
   out dx, al
   mov al, [green]
   out dx, al
   mov al, [blue]
   out dx, al

The last problem interrupts cause is keyboard and mouse reading. If you are, for example, showing a static screen with this technique of 256+ attributes, you will probably want to stop showing it when the user hits a key. With interrupts disabled, the keyboard won't generate any keys. However, this is very easy to get round. Before you check for the vertical retrace (i.e. just after you end the inner loop), simply enable interrupts, check the keyboard, then disable them again, before calling your wait_for_vertical_retrace_to_start function.

Remember, this is a very low-level technique which is not really supported by the VGA. Thus, many problems can result, including flicker, strange screens, and so on. If you get problems, you may have to experiment to find a way out.

I have a couple of ideas which I haven't yet tried myself, which I've thought of, which you may like to try. If you get any of these to work, please send me a copy of your program:

Endnote

I hope this information has been useful and inspires you to create some great programs/demos/games. Remember, if you create anything with this information, please send me a copy so I can look at it. Thanks.

If there's anything in this file which is not clear to you, or you have some problems, get in touch and I will try to help you.

Even if you don't have any problems, feel free to e-mail me. A note of thanks would be nice, and I'd like to see that I can help someone.