From: jholt@adobe.com (joe holt)
Subject: Game Techniques (was: NON-QUICKDRAW GAMES)
Organization: Adobe Systems Inc.
Date: Tue, 8 Sep 1992 00:48:21 GMT

Whoa! Slow down. All this talk of palettes is way ahead of schedule. First
we've got to talk about sync'ing to monitor retrace, opening windows and
positioning them on long word boundaries, and--and... Oh, what the heck.


PALETTES

I must be honest and say I didn't know that much about the Palette Manager
until this morning, when I read all of these palette questions and I asked
myself, "Yeah, so how does one do that?" You see, my last serious, for
profit, game endeavor was several years ago when the word on the street was
that the current implementation of the Palette Manager was best avoided.

So like any good engineer, I combed the documentation for the lowest-level
access to the video card's clut and used it. This information was available
in the first edition of "Designing Cards and Drivers for the Macintosh
Family (Starring Delta Burke)". Along with the clut calls one could find the
calls for changing a monitor's depth without the pesky Monitors control
panel. Of course this useful information has been removed from subsequent
editions of "Designing Cards" in the interest of public safety (just like
Delta Burke).

But now everything works, we've been given SetDepth(), and we should use
what the system provides. And here's what I came up with this morning:

The basic strategy is to create a window "around" whatever direct-to-screen
stuff you're going to do, and attach the appropriate palette to it. The
window does two useful things for us: it keeps the rest of the system from
drawing on top of us--that is, it sorta legitimizes our poking onto the
screen (this is what I meant in the first post about screen real estate),
and it gives us an easy place to hang a palette that is used whenever the
window is frontmost.

So let's create the window (this code assumes an 8-bit color display and
System 7):

// global stuff

#include <Palettes.h>

static WindowPtr window;
static CTabHandle clut;
static PaletteHandle palette;

..

// inside initialization code

    Rect bounds;

    SetRect( &bounds, 50, 50, 512+50, 384+50 );
    window = NewCWindow( nil, &bounds, "\pRed", true, noGrowDocProc,
        (WindowPtr) -1, true, 0 );
    SetPort( window );

And stick a palette onto it:

    clut = (CTabHandle) GetResource( 'clut', 128 );
    palette = NewPalette( 32, clut, pmAnimated+pmExplicit, 0 );
    SetPalette( window, palette, false );

The 'clut' resource is a ramp from white in index zero to 100% red in
index 31. ResEdit has a blend command which makes this easy. Once you get
this working, experiment with different values in the clut.

The flags to NewPalette() tell the Palette Manager that index zero in the
clut resource should be stuck in the video card's clut at index zero, and
so on (pmExplicit), and that no one else can use our colors (pmAnimated).
As I read Inside Mac 6, the combination of these two makes them the highest
priority as the Palette Manager is trying to shoehorn colors into the
hardware clut. Basically we're saying that colors 0 through 31 belong to
us and nobody else, and here they are. Nyah.

To see if all of this works, I draw a ramp of my colors into the window
on update events:

// In response to update events

    int c, y;
    RGBColor black;

    y = 0;
    PenSize( 10, 10 );
    black.red = black.green = black.blue = 0;
    TextSize( 9 );

    for ( c = 0; c < 32; ++c ) {
        unsigned char text[100];

        PmForeColor( c );
        MoveTo( 16, y );
        LineTo( 16384, y );

        RGBForeColor( &black );
        MoveTo( 2, y + 9 );
        NumToString( c, text );
        DrawString( text );

        y += 12;
    }
    PenNormal();

Each line in the window lists an index (0 - 31) and shows a bar of that
color. Using PmForeColor() says we want the RGB color of that particular
index--which, if all this Palette Manager stuff is working, should be the
value that's stored into screen memory. Hmmm... what if I just poked those
values into memory myself?

// Do this on mouse downs, or whenever you want

    static int c = 0;
    PixMapHandle pix;
    Ptr baseAddr, rowBase, p;
    int rowBytes, x, y;
    char mmuMode;
    Rect bounds;
    unsigned char text[100];

    SetRect( &bounds, -32000, -32000, 32000, 32000 );
    pix = (**GetMaxDevice( &bounds )).gdPMap;
    baseAddr = (**pix).baseAddr;
    rowBytes = (**pix).rowBytes & 0x3FFF;

    mmuMode = true32b;
    SwapMMUMode( &mmuMode );
    for ( rowBase = baseAddr, y = 30; y; --y ) {
        for ( p = rowBase, x = 30; x; --x ) *p++ = c;
        rowBase += rowBytes;
    }
    SwapMMUMode( &mmuMode );

    TextSize( 12 );
    SetRect( &bounds, 485, 0, 16384, 20 );
    EraseRect( &bounds );
    MoveTo( bounds.left + 5, bounds.bottom - 5 );
    NumToString( c, text );
    DrawString( text );

    c = (c+1) % 32;

I wrote this code as my mouse down handler just to give me an easy way to
trigger it. It draws a rectangle in the upper left corner of the screen by
stuffing values directly into screen memory. The value it stuffs--<c>--is
incremented each time this executes and the value is displayed in the upper
right corner of our window.

--> I'm using GetMaxDevice() because my main monitor is a 4-bit grayscale
two-page display, and my second monitor is the 8-bit one where I want the
drawing to take place. Suit to taste.

Notice that I SwapMMUMode() around the stuff that actually "draws" into
screen memory. As mentioned before, this is necessary only in certain video
configurations, but for now it doesn't hurt to be safe, and the alternative
is death by poking.

So I run this, drag the window over to my 8-bit monitor, and I see a nice
red ramp in the window, white at the top and deep red at the bottom. When I
click the mouse button a white square appears in the corner of my monitor
and the number '0' appears in the window. Each time I click the mouse the
number increments and the square gets redder. Wow.

What have I just done? In a system-friendly way, I've forced my will upon
the video hardware and blasted screen memory. I know that a zero written
into screen memory will be color zero from my clut, a one will be color
number one, etc. I know this because I TOLD IT.

Just to prove that the Palette Manager is really doing it's job, bring up
the Monitors control panel and play with the screen depth, moving the app
window from frontmost to the back and back and forth. By the way, take a look
at the small clut display at the bottom of the control panel. Is the red
ramp at the start where you think colors 0 - 31 should be? Wrong! The
Monitors control panel shows the entries in reverse order. Go figure.

Now of course to really do this right you'd want to put the red square inside
the window. Otherwise the system doesn't know we've been drawing and odd
visuals result. (For example, put the window's drag bar so that it overlaps
the red square and click the mouse button so that the square clobbers part of
the drag bar. Then move the window.) This entails finding the screen address
of the upper left corner of the *window*, and possibly aligning the window on
a long word boundary. But these will have to be subjects of another post, as
this one is already pushing the limits of my concentration.

/joe

Newsgroups: comp.sys.mac.programmer