MacOS palette woes

The latest CVS snapshot has an updated MacOS display driver, featuring
a fullscreen mode (at the current resolution), and drawing in the
window no longer writes over windows that obscure the SDL window.
Go GWorld! :slight_smile:

However, I suffer from a lack of understanding of how the Macintosh palette
manager works. I would appreciate any comments from you Mac gurus out there. :slight_smile:
(And if you see any other problems with my display driver, please let me
know!)

I’m including the relevant file…

Thanks!
-Sam Lantinga (slouken at devolution.com)

Lead Programmer, Loki Entertainment Software–
Author of Simple DirectMedia Layer -
http://www.devolution.com/~slouken/SDL/

-------------- next part --------------
/*
SDL - Simple DirectMedia Layer
Copyright © 1997, 1998 Sam Lantinga

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Sam Lantinga
5635-34 Springhouse Dr.
Pleasanton, CA 94588 (USA)
slouken at devolution.com

*/

#ifdef SAVE_RCSID
static char rcsid =
"@(#) $Id: SDL_sysvideo.c,v 1.6 1998/12/16 05:12:39 slouken Exp $";
#endif

#include <stdio.h>
#include <stdlib.h>

#include <LowMem.h>
#include <Gestalt.h>
#include <Devices.h>
#include <DiskInit.h>
#include <QDOffscreen.h>
#include <Palettes.h>

#include “SDL_video.h”
#include “SDL_error.h”
#include “SDL_endian.h”
#include “SDL_syswm.h”
#include “SDL_sysvideo.h”
#include “SDL_video_c.h”
#include “SDL_syswm_c.h”
#include “SDL_lowvideo.h”

/* Hardware surface functions */
static int SDL_LockHWSurface(SDL_Surface *surface);
static void SDL_UnlockHWSurface(SDL_Surface *surface);

/* Private display data */
static GDevice **SDL_Display = nil;
WindowRef SDL_Window = nil;
static SDL_Rect **SDL_modelist = NULL;
static CTabHandle SDL_CTab = nil;
static PaletteHandle SDL_CPal = nil;

static int SDL_UpdateVideoInfo(SDL_VideoInfo *info)
{
if ( SDL_CalculateEndian() == SDL_BIG_ENDIAN ) {
info->blit_endian = 1;
} else {
info->blit_endian = 0;
}
}

int SDL_SYS_VideoInit(SDL_PixelFormat *vformat)
{
long info;
GDevice **monitor;
Rect wrect;

/* Check out some things about the system */
Gestalt(gestaltQuickdrawVersion, &info);
if ( info == gestaltOriginalQD ) {
	SDL_SetError("Color Quickdraw not available");
	return(-1);
}

/* Initialize required managers */
InitGraf(&qd.thePort);
InitFonts();
InitWindows();
InitMenus();
InitCursor();

/* Start Macintosh events */
SDL_InitMacEvents();

/* Get a handle to the main monitor */
SDL_Display = GetMainDevice();

/* Determine pixel format */
vformat->BitsPerPixel = (**(**SDL_Display).gdPMap).pixelSize;
switch (vformat->BitsPerPixel) {
	case 16:	/* 5-5-5 RGB */
		vformat->Rmask = 0x00007c00;
		vformat->Gmask = 0x000003e0;
		vformat->Bmask = 0x0000001f;
		break;
	default:
		break;
}

/* Create our palette */
SDL_CTab = (CTabHandle)NewHandle(sizeof(ColorSpec)*256 + 8);
if ( SDL_CTab == nil ) {
	SDL_OutOfMemory();
	return(-1);
}
(**SDL_CTab).ctSeed = GetCTSeed();
(**SDL_CTab).ctFlags = 0;
(**SDL_CTab).ctSize = 255;
CTabChanged(SDL_CTab);
SDL_CPal = NewPalette(256, SDL_CTab, pmExplicit+pmTolerant, 0);

/* Get a list of available fullscreen modes */
SDL_modelist = (SDL_Rect **)malloc((1+1)*sizeof(SDL_Rect *));
if ( SDL_modelist ) {
	SDL_modelist[0] = (SDL_Rect *)malloc(sizeof(SDL_Rect));
	if ( SDL_modelist[0] ) {
		SDL_modelist[0]->x = 0;
		SDL_modelist[0]->y = 0;
		SDL_modelist[0]->w = (**SDL_Display).gdRect.right;
		SDL_modelist[0]->h = (**SDL_Display).gdRect.bottom;
	}
	SDL_modelist[1] = NULL;
}

/* Fill in some window manager capabilities */
SDL_HWCaps.info.wm_available = 1;
SDL_WMCaps.SetCaption = SDL_SYS_SetWMCaption;

/* Fill in our hardware acceleration capabilities */
SDL_UpdateVideoInfo(&SDL_HWCaps.info);
SDL_HWCaps.LockHWSurface = SDL_LockHWSurface;
SDL_HWCaps.UnlockHWSurface = SDL_UnlockHWSurface;

return(0);

}

SDL_Rect **SDL_SYS_ListModes(SDL_Surface *screen,
SDL_PixelFormat *format, Uint32 flags)
{
if ( screen->format->BitsPerPixel == format->BitsPerPixel ) {
if ( (flags & SDL_FULLSCREEN) == SDL_FULLSCREEN ) {
return(SDL_modelist);
} else {
return((SDL_Rect **)-1);
}
} else {
return((SDL_Rect **)0);
}
}

static short mBarHeight;

static void HideMenuBar(void)
{
if ( LMGetMBarHeight() > 0 ) {
mBarHeight = LMGetMBarHeight();
LMSetMBarHeight(0);
}
}
static void ShowMenuBar(void)
{
if ( LMGetMBarHeight() == 0 ) {
LMSetMBarHeight(mBarHeight);
}
}

/* Various screen update functions available */
static void SDL_WindowUpdate(SDL_Surface *screen,int numrects,SDL_Rect *rects);
static void SDL_DirectUpdate(SDL_Surface *screen,int numrects,SDL_Rect *rects);

static void SDL_SYS_UnsetVideoMode(SDL_Surface current)
{
/
Free the current window, if any */
if ( SDL_Window != nil ) {
GWorldPtr memworld;

	memworld = (GWorldPtr)GetWRefCon(SDL_Window);
	if ( memworld != nil ) {
		UnlockPixels(GetGWorldPixMap(memworld));
		DisposeGWorld(memworld);
	}
	CloseWindow(SDL_Window);
	if ( (current->flags & SDL_FULLSCREEN) == SDL_FULLSCREEN ) {
		ShowMenuBar();
	}
	SDL_Window = nil;
}
current->pixels = NULL;
current->flags &= ~(SDL_HWSURFACE|SDL_FULLSCREEN);

}

SDL_Surface *SDL_SYS_SetVideoMode(SDL_Surface *current,
Uint16 width, Uint16 height, Uint8 bpp, Uint32 flags)
{
Rect wrect;

/* Free any previous video mode */
SDL_SYS_UnsetVideoMode(current);

/* Create the Mac window and SDL video surface */
current->flags &= SDL_THREADSAFE;
current->w = width;
current->h = height;
SetRect(&wrect, 0, 0, width, height);
OffsetRect(&wrect,
	(SDL_modelist[0]->w-width)/2, (SDL_modelist[0]->h-height)/2);

if ( (flags & SDL_FULLSCREEN) == SDL_FULLSCREEN ) {
	current->flags |= SDL_HWSURFACE|SDL_FULLSCREEN;
	SDL_Window = NewCWindow(nil, &wrect, "\p", true, plainDBox,
					(WindowPtr)-1, false, 0);
	current->pitch = (**(**SDL_Display).gdPMap).rowBytes & 0x3FFF;
	current->pixels = (**(**SDL_Display).gdPMap).baseAddr;
	HideMenuBar();
	SDL_SYS_UpdateRects = SDL_DirectUpdate;
} else {
	GWorldPtr memworld;
	PixMapHandle pixmap;
	SDL_Window = NewCWindow(nil, &wrect, "\p", true, noGrowDocProc,
					(WindowPtr)-1, true, 0);
	SetPalette(SDL_Window, SDL_CPal, false);
	ActivatePalette(SDL_Window);
	if ( NewGWorld(&memworld, 0, &SDL_Window->portRect, SDL_CTab,
						nil, 0) != noErr ) {
		SDL_SetError("NewGWorld() failed");
		return(NULL);
	}
	SetWRefCon(SDL_Window, (long)memworld);
	pixmap = GetGWorldPixMap(memworld);
	LockPixels(pixmap);
	current->pitch = (**pixmap).rowBytes & 0x3FFF;
	current->pixels = GetPixBaseAddr(pixmap);
	SDL_SYS_UpdateRects = SDL_WindowUpdate;
}
SetPort(SDL_Window);
SelectWindow(SDL_Window);

/* We're live! */
return(current);

}

static int SDL_LockHWSurface(SDL_Surface *surface)
{
return(0);
}
static void SDL_UnlockHWSurface(SDL_Surface *surface)
{
return;
}

void SDL_DirectUpdate(SDL_Surface *screen, int numrects, SDL_Rect rects)
{
/
The application is already updating the visible video memory */
return;
}

void SDL_WindowUpdate(SDL_Surface *screen, int numrects, SDL_Rect *rects)
{
GWorldPtr memworld;
WindowPtr saveport;
int i;
Rect update;

/* Copy from the offscreen GWorld to the window port */
GetPort(&saveport);
SetPort(SDL_Window);
memworld = (GWorldPtr)GetWRefCon(SDL_Window);
for ( i=0; i<numrects; ++i ) {
	update.left = rects[i].x;
	update.right = rects[i].x+rects[i].w;
	update.top = rects[i].y;
	update.bottom = rects[i].y+rects[i].h;
	CopyBits(&((GrafPtr)memworld)->portBits, &SDL_Window->portBits,
					&update, &update, srcCopy, nil);
}
SetPort(saveport);

}

int SDL_SYS_SetColors(SDL_Surface *screen, int firstcolor, int ncolors)
{
SDL_Palette *palette;
CTabHandle cTab;
GWorldPtr memworld;
int i;

/* Get the colortable from the either the display or window */
if ( (screen->flags & SDL_FULLSCREEN) == SDL_FULLSCREEN ) {
	cTab = (**(**SDL_Display).gdPMap).pmTable;
} else {
	cTab = SDL_CTab;
}

/* Verify the range of colors */
if ( (firstcolor+ncolors) > ((**cTab).ctSize+1) ) {
	return(0);
}

/* Set the screen palette and update the display */
palette = screen->format->palette;
for ( i=firstcolor; i<(firstcolor+ncolors); ++i ) {
	(**cTab).ctTable[i].value = i;
	(**cTab).ctTable[i].rgb.red =
		(palette->colors[i].r << 8) | palette->colors[i].r;
	(**cTab).ctTable[i].rgb.green =
		(palette->colors[i].g << 8) | palette->colors[i].g;
	(**cTab).ctTable[i].rgb.blue =
		(palette->colors[i].b << 8) | palette->colors[i].b;
}
if ( (screen->flags & SDL_FULLSCREEN) == SDL_FULLSCREEN ) {
	GDevice **odisplay;
	odisplay = GetGDevice();
	SetGDevice(SDL_Display);
	SetEntries(0, (**cTab).ctSize, (ColorSpec *)&(**cTab).ctTable);
	SetGDevice(odisplay);
}
return(1);

}

void SDL_SYS_VideoQuit(SDL_Surface *screen)
{
int i;

/* Free current video mode */
SDL_SYS_UnsetVideoMode(screen);

/* Free palette and restore original one */
if ( SDL_CTab != nil ) {
	DisposeHandle((Handle)SDL_CTab);
	SDL_CTab = nil;
}
if ( SDL_CPal != nil ) {
	DisposePalette(SDL_CPal);
	SDL_CPal = nil;
}
RestoreDeviceClut(GetMainDevice());

/* Free list of video modes */
if ( SDL_modelist != NULL ) {
	for ( i=0; SDL_modelist[i]; ++i ) {
		free(SDL_modelist[i]);
	}
	free(SDL_modelist);
	SDL_modelist = NULL;
}

}

void SDL_SYS_FinalQuit(void)
{ }

However, I suffer from a lack of understanding of how the Macintosh palette
manager works. I would appreciate any comments from you Mac gurus out
there.

I’d love to dedicate some time and write up a better implementation myself,
but my non-Ambrosia time is rather limited currently. Let me pass along a
few observations, for what they are worth.

  • There are 3 types of “video modes”: direct (16 or 32bpp), clut (1 to 8bpp),
    and fixed clut (ala LCD, usually monochrome or 8bpp). Look at the PixMap’s
    pixelType field.
  • On direct modes, you don’t use color tables or palettes at all. Pixels
    on the screen are just (1)555 or (8)888.
  • On clut devices (that aren’t fixed), you can use the Palette Manager to be
    friendly about colors with other windows. On the other hand, if you don’t
    care (if you are in fullscreen) you can muck with the display’s color table
    yourself – the latter is what most Ambrosia games do.
  • HideMenuBar() and ShowMenuBar() also need to modify the gray region. See
    ftp://ftp.apple.com/developer/Sample_Code/Toolbox/HideMenubarEtc.sit.hqx
    <ftp://ftp.apple.com/developer/Sample_Code/
    Snippets/Toolbox/HideMenuBar.sit.hqx>

If you have a few specific questions, I can try and answer them.

Matt

/* Matt Slot, Bitwise Operator * One box, two box, yellow box, blue box. *

I’d love to dedicate some time and write up a better implementation myself,
but my non-Ambrosia time is rather limited currently. Let me pass along a
few observations, for what they are worth.

Thanks for your advice, I appreciate it. :slight_smile:

  • On direct modes, you don’t use color tables or palettes at all. Pixels
    on the screen are just (1)555 or (8)888.

Got this, it works fine.

  • On clut devices (that aren’t fixed), you can use the Palette Manager to be
    friendly about colors with other windows. On the other hand, if you don’t
    care (if you are in fullscreen) you can muck with the display’s color table
    yourself – the latter is what most Ambrosia games do.

Setting the palette directly works okay, and SDL does that in fullscreen
mode. Sometimes the font appears to be corrupted, but this is apparently
a side effect of SetEntries() as it invalidates the font cache (?) I could
be mistaken here. If it shouldn’t corrupt the display font, could you send
me an example of code that works? I recently found the original Maelstrom
mac code, so I’ll probably take a look at that.

What I have a problem with is getting the Palette Manager to do what I want.
It’s mostly just ignorance on my part, but there seems to be very little
publicly available code that does fast drawing using the Palette Manager.

I forgot about fixed palette devices. I can easily support those in SDL.

I’ll take a look.

Thanks again for your help. I’ll lose my development Mac on Monday or
Tuesday, but Loki Entertainment will be supporting Linux PPC versions
of our games, so I should have access to a Mac development environment
again soon.

See ya!
-Sam Lantinga (slouken at devolution.com)

Lead Programmer, Loki Entertainment Software–
Author of Simple DirectMedia Layer -
http://www.devolution.com/~slouken/SDL/