I ported an OpenGL application from GLUT to SDL just recently which is
also meant to be used as a screensaver on Mac OS X. This application is
launched by a 3rd party software that successfully launches the old GLUT
version but fails to do so with the new SDL port.
I had to do this for Feeding Frenzy (which installs a copy of itself as
a screensaver)…on Windows, it’s just the game .exe run with a
commandline switch; on Mac, there was a LOT of tapdancing involved.
Here’s what I did to make this work. I’m not sure it was the best
approach, but it was the only way I could find to make it happen. Maybe
some of this will be useful.
I think that, ultimately, there was only a very small portion of SDL
that needed to be used in the screen saver. The game used all sorts of
things, but I suspect that the screen saver really only used
SDL_GetTicks(). I never call SDL_SetVideoMode() for the screensaver. You
might be better off #ifdef’ing the SDL code if it’s just a few small
things, like timers; on deadline, I figured I’d risk leaving the SDL
calls in, rather than try to weed them all out.
So, uh, here’s the basic rundown:
-
You can’t link to SDL directly. I had it shipped with the game
(@executable_path/libSDL.dylib), but “@executable_path” is meaningless
for screensavers, since your saver is loaded into someone else’s process
with a different path. I ended up using dlopen() on the SDL library at
startup and picking out the symbols I needed, and then calling SDL_Init().
-
Your mainline doesn’t run. You have to supply an (Objective-C!)
subclass of ScreenSaverView, which will be a little glue between the Mac
interfaces and your own screensaver, which will be mostly unchanged.
System Preferences will load your bundle and run the saver in a little
preview window, and a different process will load it fullscreen as the
actual screensaver later. This subclass also dictates whether System
Preferences provides a configuration dialog for the saver, when it
should redraw, etc.
-
You can use OpenGL, but you shouldn’t use any SDL calls that depend on
the existance of a valid GL context. You might be able to use the SDL
window id hack, but I’d avoid it. All of your effort is going to be in
standard GL calls anyhow, and the GL context is prepared simply enough
here, so there isn’t really any SDL video things you need.
-
You don’t swap buffers, you just call glFlush() …(apparently.)
-
link the saver with “-framework ScreenSaver -bundle” … I think you
give it an Info.plist and put it all in “~/Library/Screen Savers” but I
don’t have those details in front of me.
Here’s a cutdown version of my glue code:
#import <ScreenSaver/ScreenSaver.h>
#import <GL/gl.h>
@interface FeedingFrenzyScreenSaverView : ScreenSaverView
{
// So what do you need to make an OpenGL screen saver? Just an
NSOpenGLView (or subclass thereof)
// So we’ll put one in here.
NSOpenGLView *glView;
// these were for the options dialog in System Preferences. Ignore.
IBOutlet id configureSheet;
IBOutlet id okButton;
IBOutlet id cancelButton;
IBOutlet id saverDropDown;
IBOutlet id nextSaverText;
}
- (IBAction)hitOK:(id)sender;
- (IBAction)hitCancel:(id)sender;
- (IBAction)changedSaver:(id)sender;
@end
// This overrides NSOpenGLView to make sure the viewport is sane.
@implementation NSOpenGLView (FeedingFrenzyUpdateOverride)
- (void) update
{
NSRect rectView = [self bounds];
glViewport(0, 0, rectView.size.width, rectView.size.height);
}
@end
// this is used to load SDL.
int LoadDylibStubs(void);
@implementation FeedingFrenzyScreenSaverView
// this is your startup function.
-
(id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
if (!LoadDylibStubs())
return NULL;
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
// So we modify the setup routines just a little bit to get our
// new OpenGL screensaver working.
// Create the new frame
NSRect newFrame = frame;
// Slam the origin values
newFrame.origin.x = 0.0;
newFrame.origin.y = 0.0;
// Now alloc and init the view, right from within the screen
saver’s initWithFrame:
glView = [[NSOpenGLView alloc] initWithFrame:newFrame];
// If the view is valid, we continue
if(glView)
{
// Make sure we autoresize
[self setAutoresizesSubviews:YES];
long swapInt = 1;
[[glView openGLContext] setValues:&swapInt
forParameter:NSOpenGLCPSwapInterval]; // set to vbl sync
// We make it a subview of the screensaver view
[self addSubview:glView];
[[glView openGLContext] makeCurrentContext];
[self setAnimationTimeInterval:1/60.0];
}
else // Log an error if we fail here
NSLog(@"Error: OpenGL Screen Saver failed to initialize
NSOpenGLView!");
}
// Finally return our newly-initialized self
return self;
}
// this is called when the screen saver is actually starting (not
// initializing; that’s elsewhere.)
-
(void)startAnimation
{
[super startAnimation];
[[glView openGLContext] makeCurrentContext];
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
if (firstView == NULL)
firstView = self;
if (firstView == self)
GatsuScreenSaverInit();
}
// this is when it’s ending
-
(void)stopAnimation
{
[super stopAnimation];
[[glView openGLContext] makeCurrentContext];
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
GatsuScreenSaverShutdown();
}
-
(void)drawRect:(NSRect)rect
{
}
// this is called when it wants you to redraw for the next frame. It’s
// called frequently.
- (void)animateOneFrame
{
[[glView openGLContext] makeCurrentContext];
GatsuScreenSaverUpdate();
}
// Make this return NO if you don’t want a configure dialog in System
// Preferences.
-
(BOOL)hasConfigureSheet
{
return YES;
}
-
(NSWindow*)configureSheet
{
if (configureSheet != nil)
return configureSheet;
// Build your configuration dialog here.
return configureSheet; // let the user tweak!
}
-
(IBAction)hitOK:(id)sender // user hit OK button.
{
ffSetSaverSelection([saverDropDown indexOfSelectedItem]);
[NSApp endSheet:configureSheet]; // kill the dialog.
}
-
(IBAction)hitCancel:(id)sender // user hit Cancel button.
{
[NSApp endSheet:configureSheet]; // just kill the dialog.
}
-
(IBAction)changedSaver:(id)sender
{
// no-op.
}
@end
–ryan.