By Aaron Severn

This article is very advanced. I recommend using it only if you know a LOT about Qbasic- editor. Continued from Issue 1

2.3 Setting a screen mode

-------------------------

Once you have confirmed the presence of VESA support, the next step is to set a mode. For a list of VESA defined screen modes see appendix B. The set SVGA mode function is function &H02. You will also need to run function &H01 to return info on the mode. Like function &H00 it will fill a variable with info, this time on the screen mode. The following is the type definition for the info returned by function &H01.

    TYPE ModeInfoBlock
        ModeAttributes AS INTEGER
        WinAAttributes AS STRING * 1
        WinBAttributes AS STRING * 1
        WinGranularity AS INTEGER
        WinSize AS INTEGER
        WinASegment AS INTEGER
        WinBSegment AS INTEGER
        WinFuncPtr AS LONG
        BytesPerScanLine AS INTEGER
        XResolution AS INTEGER
        YResolution AS INTEGER
        XCharSize AS STRING * 1
        YCharSize AS STRING * 1
        NumberOfPlanes AS STRING * 1
        BitsPerPixel AS STRING * 1
        NumberOfBanks AS STRING * 1
        MemoryModel AS STRING * 1
        BankSize AS STRING * 1
        NumberOfImagePages AS STRING * 1
        Rsvd AS STRING * 1
        RedMaskSize AS STRING * 1
        RedFieldPosition AS STRING * 1
        GreenMaskSize AS STRING * 1
        GreenFieldPosition AS STRING * 1
        BlueMaskSize AS STRING * 1
        BlueFieldPosition AS STRING * 1
        RsvdMaskSize AS STRING * 1
        DirectColorModeInfo AS STRING * 1
        Reserved AS STRING * 216
    END TYPE

For a complete description of what everything is for see appendix A. The important fields are as follows.

    ModeAttributes     - will let you know if the mode is available or not
    WinGranularity     - either 4 or 64, this defines the size of the banks
                          in the mode, more on this later.
    WinASegment        - the start segment of the graphics buffer, usually
                          &HA000
    XResolution        - speaks for itself
    YResolution        - what do you think?
    BitsPerPixel       - how many bits are required for one pixel, useful in
                          determining how many colours are available
                          colours = 2 ^ BitsPerPixel
    NumberOfImagePages - the number of video memory pages available in the
                          mode, this changes from computer to computer
                          depending on how much video memory is available.
                          This value is actually the number of pages minus 1.


So, anyway, when setting an SVGA screen mode, the following should be done. Again, sample code is available in appendix D.
    1. DIM a variable of type ModeInfoBlock.
    2. Set AX to &H4F01, VESA function &H01.
    3. Set CX to the mode number (listed in appendix B).
    4. Set ES to the segment address of the ModeInfoBlock variable.
    5. Set DI to the offset address of the ModeInfoBlock variable.
    6. Generate interrupt &H10, remember to use CALL INTERRUPTX for ES.
    7. Check (ModeAttributes AND 1), this should be 0.
    8. Set AX to &H4F02, VESA function &H02.
    9. Set BX to the mode number.
   10. Generate interrupt &H10.
   11. Check the value returned in AX, it should be &H4F.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

3 Basic Graphics Functions

--------------------------

3.1 Setting pixels

------------------

3.1.1 Bank switching

--------------------

Remember way back in the introduction when I explained that you only had 64k of video memory in the early days of PC graphics. Well, in some ways that hasn't changed. Now you can only have 64k of video memory at a time. Since SVGA modes often require well over a megabyte of video memory, this creates a problem. You can't possibly have access to all the video memory at once. The solution is bank switching.

The SVGA video memory is arranged in banks of either 4k or 64k (determined by the value in WinGranularity). When working with SVGA, you have to choose which bank you want to write to. The VESA supplies a nice little function that will let you do just that. It is VESA function &H05 and works as follows.

    1. Set AX to &H4F05.
    2. BX does have a purpose, but generally you don't need to use it, thus
        set BX to 0.
    3. Set DX to the bank you want to write to.
    4. Generate interrupt &H10.

There is one further detail that should be discussed about bank switching. The window granularity plays a big part in it. Some cards have 4k banks for the purpose of faster bank switching, I won't go into detail on how this can be achieved. Unfortunately, a lot of cards use 64k granularity, so if you write the software specifically for 4k granularity it won't work on many computers. Thus it's a good idea to simulate a 64k bank when you have a 4k bank. To simulate 64k banks in all granularities, divide 64 by WinGranularity and save this value for later use (winGran = 64 / WinGranularity). Then when switching banks, multiply the bank you want by the value you calculated (bank = winGran * bank) and use that value in DX. This will effectively simulate 64k banks even if 4k banks are in use.

3.1.2 256 colour modes

----------------------

Pixels in 256 colour SVGA modes are stored exactly like they are in VGA mode &H13 (SCREEN 13 in QBasic), one byte = one pixel with pixels colours defined in the same palette that mode &H13 uses. The only difference is the bank switching that must be performed as explained above. So here's what you should do.

    1. Make sure that the default segment set by DEF SEG is pointing at the
        graphics buffer (usually &HA000, the value is contained in the field
        WinASegment of the ModeInfoBlock).
    2. Calculate the offset of the pixel the same way that you would in
        SCREEN 13 (offset& = y * xRes + x).  Note that offset& will be a
        long integer, and must be treated as such.
    3. Find out what bank the pixel is on by dividing the offset by &H10000,
        effectively a bit shift of 16 to the left (bank = offset& \ &H10000).
        Note integer division is used (backslash).
    4. Compare the value found for the bank with a variable that holds the
        current bank.  If it is different then perform a bank switch the way
        described above.  Use the method that simulates 64k banks even if
        they are 4k.
    5. Change the offset so that it is within the range of an unsigned
        integer (offset& = offset& AND &HFFFF&).  Note that since QBasic uses
        signed integers, you will still need to hold the offset in a long
        integer variable.
    6. POKE the colour at the desired offset.   

Again, sample code is available in appendix D if you need help.

3.1.3 Direct colour modes

-------------------------

Direct colour modes are the SVGA modes that allow up to 16.8 million colours. This section will discuss two types of these modes, the 15-bit modes (32k colours) and the 16-bit modes (64k colours). The name direct colour was chosen for a reason. Unlike 256 colour modes which have a customizable palette that can hold 256 different colours, direct colour modes have no palette but instead accept red, green, and blue components (rgb) directly. In 15-bit modes, each component is 5 bits long, thus it can have any value from 0 to 31, with one bit left unused. 16-bit modes use the extra bit in the green component which is 6 bits long and can have any value from 0 to 63. Both of these mode types require 2 bytes of memory for each pixel, for that reason the simple offset = y * xRes + x formula won't work. Instead you need to use offset = (y * xRes * 2) + (x * 2). The multiplications by 2 adjust for the fact that each pixel fills 2 bytes. Other than that, setting pixels is the same as in 256 colour modes until it comes to writing the actual values to the video memory. You need to combine the red, green, and blue components into two bytes and POKE them both in one after the other. Here's how to combine the components.

  For 15-bit modes:
    highByte = red * 4 + green \ 8
    lowByte = (green AND 7) * 32 + blue

  For 16-bit modes:
    highByte = red * 8 + green \ 8
    lowByte = (green AND 7) * 32 + blue

That's just a series of bit shifts to put the components in their proper places. Then you just POKE them in at the calculated offset like so.

    POKE offset&, lowByte
    POKE offset& + 1, highByte

And that's how to set pixels in 15-bit and 16-bit direct colour modes.

3.2 Page flipping

-----------------

In order to achieve good smooth animation, page flipping is almost a must. Many of us, though, don't know what it is since we've grown up using QBasic's SCREEN 13 for the 256 colours, which doesn't support page flipping. The concept is pretty simple, usually screen modes don't use up the entire video memory, and often they use less than half. Rather than let the rest of the memory go to waste, it can be used a little like a second screen. You display one page while drawing graphics on the other, then you display the page you were drawing on and start drawing on the other one, and just keep on swapping pages. The result is, the user can't see you drawing the graphics so animation looks more smooth. As explained before, the number of pages available in each screen mode depends on how much video memory is available. To get the number of pages available take the value in NumberOfImagePages of the ModeInfoBlock and add 1.

3.2.1 Setting the active page

-----------------------------

This is done with a bit of a trick. There's no function that will choose which page you want to write to. You can write to any page at any time. So here's the trick, when writing to page 0, y = y, when writing to page 1, y = y + yResolution, when writing to page 2, y = y + yResolution * 2, and so on. In general, y = y + yResolution * page. This is really all you need to do.

3.2.2 Setting the visible page

------------------------------

Now this is a little more complicated. There's a VESA function that lets you choose the first vertical scan line that you want to view. In order to view page 0 this value would be 0, for page 1 it would be yResolution, for page 2 it would be yResolution * 2, and so on. Again, in general terms firstLine = page * yResolution. So here's the details.

    1. Set AX to &H4F07, VESA function &H07.
    2. Set BX and CX to 0.  These do have a purpose, but for page flipping
        they should be 0.
    3. Set DX to page * yResolution.
    4. Generate interrupt &H10.

3.3 Getting enough speed

------------------------

The pixel setting graphics functions described above will work just fine, however they will be way too slow for most practical purposes. The only solution is to write other, more useful functions in assembly language. Then you will be able to achieve enough speed. An example of a put routine for 256 colour SVGA modes is available in appendix D as an example of what must be done to make useful SVGA graphics functions. If you really know what you're doing, you should be able to write your own such functions. If not, well I guess you'll have to learn. If you've read all the way down to here you must be committed to learning this, or you should be committed. Either way, I hope this helped. Next issue, we'll provide some assembly code and...an actual program for Qbasic SVGA! Tune in!

Back to Top





This tutorial originally appeared in QBasic: The Magazine Issue 2.