From: jholt@adobe.com (joe holt)
Newsgroups: comp.sys.mac.games,comp.sys.mac.programmer
Subject: Re: NON-QUICKDRAW GAMES
Date: 3 Sep 92 21:28:02 GMT
Organization: Adobe Systems Inc.

This is a fun subject, and I'd like to help spread the religion. Let me add
a few bits:

To begin, insert all of the reasons *not* to draw directly to the screen.
Would someone like to enumerate them? My favorite is that this can cause the
GC card to crash. As if it needed my help. :-)

Personally, it bothers me to hear people cry about how dangerous it is to
bypass QuickDraw, as if it's a matter of personal safety (you'll go blind).
Most likely these people have never done it, or at least not to the extent
that you do when really writing a game (or other application--did you know
that ATM draws straight to the screen?). It can be scary as you're learning,
because there are a few gotchas. Having done it, however, you realize that
the entire strategy is easy to understand. All of the techniques are old old
old game programmer tricks--there is nothing proprietary about them. What's
new is the way they're used on the Mac, and especially the way you get around
some of the Mac's video quirks.

It may not be obvious to everyone that the Mac's video screen is memory-
mapped. That means that the pixels on the screen are also memory locations in
RAM. By writing values into this RAM, you're also playing with pixels on the
screen. This memory is real RAM: you can read the values back that you put in,
and the computer can use it just like any other kind of RAM. In fact, you can
even put programs in the video RAM. Of course, the screen would look like
garbage, but what the hey, it'd be fun. (This used to be a copy protection
technique on the Apple II--put code in the text video RAM. As soon as you drop
into the monitor to hack the code, it scrolls off the screen). Actually,
more interesting would be to put a program's variables in video RAM, and
watch the colors change as the program ran. Now there's an INIT waiting to be
written.

Anyway, so the first question is, where in RAM is the screen?

This turns out to be an easy thing to find out, although the method is
different for Color QuickDraw and non-Color QuickDraw machines. So, the very
first thing you need to do is call SysEnvirons() to find out if the machine
has Color QuickDraw:

    SysEnvRec This_mac;

    SysEnvirons( 1, &This_mac );

Now you can get the base address like this:

    Ptr base_address;

    if ( This_mac.hasColorQD ) {
        GDHandle the_monitor;

        the_monitor = GetMainDevice();
        base_address = (**(**the_monitor).gdPMap).baseAddr;
    }
    else base_address = ScrnBase; /* low-memory global long at 0x824 */

A few things to mention right away. The recommended way to get a GDevice's
base address is slightly different under System 7, but the above will work.
Also, this will just give you the base address of the monitor with the menu
bar on it. This will make those lucky few with multiple monitors angry if
their other monitor is the one they want to play the game on. I'm also
glossing over things like #include files and such.

What happens if you actually use this pointer and stick something there? Try
this:

    int i;
    Ptr p;

    for ( p = base_address, i = 50; i; --i )
        *p++ = 0xAA;

    while ( !Button() );    /* wait for mouse click */
    while ( Button() );

Did anything happen? If your monitor is in black and white, you probably got
a line of alternating pixels across the very top line of the menu bar. If your
monitor is in color, that line is shorter and probably made up of colored pixels.
Rerun this at different monitor depths.

Did your Mac crash instead? Oops. Here's the first gotcha: some video cards,
mostly the newer ones with millions of colors, need to be addressed in 32-bit
mode. If you don't switch to 32-bit addressing before reading or writing into
video RAM, you'll either get an ADDRESS ERROR or clobber other parts of memory
with dire results. The reasons are really arcane and I'll be happy to explain
them, but for now this info will just get in the way of the real fun that
we're having (we are having fun, aren't we?). The little loop above would turn
into this:

    int i;
    Ptr p;
    char old_MMUMode;

    if ( This_mac.hasColorQD ) {
        old_MMUMode = true32b;
        SwapMMUMode( &old_MMUMode );
    }

    for ( p = base_addresss, i = 50; i; --i )
        *p++ = 0xAA;

    if ( This_mac.hasColorQD )
        SwapMMUMode( &old_MMUMode );

You see, what you have to do is set the Mac to 32-bit addressing and save the
previous addressing mode, do your screen stuff, then set it back to the
previous state. While in 32-bit addressing mode, you shouldn't make any ROM
calls (mostly) or dereference handles and the like. The rule-of-thumb is to
get back out of 32-bit addressing as soon as you can. Of course, if you have
System 7 and your Mac is already in 32-bit addressing mode, the above code
will work but the SwapMMUMode()'s are unnecessary. Same is true if the video
card doesn't need 32-bit addressing. To do it right, you really should check
all of these things.

You have just drawn directly to the screen. Hurrah!

As Ben mentioned, there are good sources for this kind of thing, although not
everyone gets  d e v e l o p  magazine. If there is sufficient interest, we
should all pitch in and turn this into a real thread. Things to cover include:

  1. setting up a vbl task
  2. uses and abuses of off-screen buffers
  3. clut animation
  4. protecting your screen real estate
  5. masking techniques
  6. assembly language optimizations
  7. <add yours here>

Let's see how far our collective wisdom can take us...

/joe