The problem is that SDL uses a pull messaging system, but iOS requires a pull system. Here is how to get the two working together correctly.
(I am using this on a project that is on PC and iOS)
This only change that I made to SDL was as follows
Code:
void
UIKit_PumpEvents(_THIS)
{
return;
/*
When the user presses the ‘home’ button on the iPod
the application exits – immediatly.
Unlike in Mac OS X, it appears there is no way to cancel the termination.
This doesn't give the SDL user's application time to respond to an SDL_Quit event.
So what we do is that in the UIApplicationDelegate class (SDLUIApplicationDelegate),
when the delegate receives the ApplicationWillTerminate message, we execute
a longjmp statement to get back here, preventing an immediate exit.
*/
if (setjmp(*jump_env()) == 0) {
/* if we're setting the jump, rather than jumping back */
SInt32 result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE);
} while (result == kCFRunLoopRunHandledSource);
}
}
This stops SDL processing all the events ( I added a return at the top of the function ). This fixes SDL crashing some times when other views are laid over the top. The problem of needing the long jump is avoided as this message will be processed when iOS sends the message and not during the next update.
The next thing that needs to be done is to intercept the events as they occur, fortunately this is an easy thing to do. Create a callback function and inform SDL, like so. Always return 0, this will stop SDL from storing the event.
Code:
int SystemEventCallback(SDL_Event* event)
{
MyEventHandler::instance().OnEvent(event);
return 0;
}
// Add this line to the initialisation code.
SDL_SetEventFilter(EventCallback, NULL);
The event handler looks something like this. (Remember that the events are processed as they occur, so save results when needed)
Code:
void EventHandler::OnEvent(SDL_Event *aEvent)
{
switch (aEvent->type)
{
case SDL_WINDOWEVENT:
{
switch (aEvent->window.event)
{
case SDL_WINDOWEVENT_FOCUS_LOST:
OnLostFocus();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
OnGainedFocus();
break;
}
break;
}
}
}
Not sure these events work I applied the patch http://forums.libsdl.org/viewtopic.php?t=7733&sid=c9ade150b99a01c8f080cdd465f21b11 to this code so I use the SDL_SYSEVENT_WILL_SUSPEND and SDL_SYSEVENT_WILL_SUSPEND events.
So now we are half done, the other problem is that SDL runs in a tight loop. It is time to change this to an event driven approach. First SDL_uikitopenglview needs to be modified. Create a new objective c class and implement as follows. (Header first)
Code:
#import <UIKit/UIKit.h>
#import “./sdl/src/video/uikit/SDL_uikitopenglview.h”
@interface SDL_uikitopenglview (MYOpenGLView)
-
(void)startAnimation;
-
(void)stopAnimation;
-
(void)doLoop:(id)sender;
@end
Code:
#import “MYOpenGLView.h”
#import <OpenGLES/EAGLDrawable.h>
#import <QuartzCore/QuartzCore.h>
#include “SDL.h”
// a file that will let me use c++ for SDL
#include “SystemCalls.h”
id displayLink;
NSInteger animationFrameInterval = 1;
@implementation SDL_uikitopenglview (MYOpenGLView)
// make an exact copy of the SDL function
-
(id)initWithFrame:(CGRect)frame
retainBacking:(BOOL)retained
rBits:(int)rBits
gBits:(int)gBits
bBits:(int)bBits
aBits:(int)aBits
depthBits:(int)depthBits
stencilBits:(int)stencilBits
majorVersion:(int)majorVersion
{
depthBufferFormat = 0;if ((self = [super initWithFrame:frame])) {
const BOOL useStencilBuffer = (stencilBits != 0);
const BOOL useDepthBuffer = (depthBits != 0);
NSString *colorFormat = nil;if (rBits == 8 && gBits == 8 && bBits == 8) { /* if user specifically requests rbg888 or some color format higher than 16bpp */ colorFormat = kEAGLColorFormatRGBA8; } else { /* default case (faster) */ colorFormat = kEAGLColorFormatRGB565; } /* Get the layer */ CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; eaglLayer.opaque = YES; eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: retained], kEAGLDrawablePropertyRetainedBacking, colorFormat, kEAGLDrawablePropertyColorFormat, nil]; if (majorVersion > 1) { context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2]; } else { context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES1]; } if (!context || ![EAGLContext setCurrentContext:context]) { [self release]; SDL_SetError("OpenGL ES %d not supported", majorVersion); return nil; } // !!! FIXME: use the screen this is on! /* Use the main screen scale (for retina display support) */ if ([self respondsToSelector:@selector(contentScaleFactor)]) self.contentScaleFactor = [UIScreen mainScreen].scale; /* create the buffers */ glGenFramebuffersOES(1, &viewFramebuffer); glGenRenderbuffersOES(1, &viewRenderbuffer); glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer]; glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); if ((useDepthBuffer) || (useStencilBuffer)) { if (useStencilBuffer) { /* Apparently you need to pack stencil and depth into one buffer. */ depthBufferFormat = GL_DEPTH24_STENCIL8_OES; } else if (useDepthBuffer) { /* iOS only has 24-bit depth buffers, even with GL_DEPTH_COMPONENT16_OES */ depthBufferFormat = GL_DEPTH_COMPONENT24_OES; } glGenRenderbuffersOES(1, &depthRenderbuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); glRenderbufferStorageOES(GL_RENDERBUFFER_OES, depthBufferFormat, backingWidth, backingHeight); if (useDepthBuffer) { glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); } if (useStencilBuffer) { glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); } } if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { return NO; } /* end create buffers */ self.autoresizingMask = 0; // don't allow autoresize, since we need to do some magic in -(void)updateFrame. [self startAnimation];
}
return self;
} -
(void)startAnimation
{
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against earlier versions will result in a warning, but can be dismissed
// if the system version runtime check for CADisplayLink exists in -initWithCoder:.displayLink = [NSClassFromString(@“CADisplayLink”) displayLinkWithTarget:self selector:@selector(doLoop:)];
[displayLink setFrameInterval:animationFrameInterval];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
} -
(void)stopAnimation
{
[displayLink invalidate];
displayLink = nil;
} -
(void)doLoop:(id)sender
{
runApplicationFrame();
}
@end
The initWithFrame function is a copy of the function in SDL with one exception [self startAnimation];. This is the important part as it sets up the way that we do a loop by callback. The start animation function basically sets up the doLoop function to run every x number of frames. Set this animationFrameInterval variable to 1 to run every frame, 2 for every 2nd frame etc. For more details (and other ways to do this) http://www.ananseproductions.com/game-loops-on-ios/.
Call the stopAnimation function when the application is suspended; and call the startAnimation function when the application resumes.
Lastly setup the function to run the application runApplicationFrame();.
Code:
void runApplicationFrame()
{
// iOS uses a push event system instead of a pull event system
#ifdef WIN32
SDL_Event Event;
while (SDL_PollEvent(&Event))
{
OnEvent(&Event);
}
#else
/* Check for joystick state change */
SDL_JoystickUpdate();
#endif // WIN32
// do update stuff here
// do render stuff here
SDL_RenderClear(mRenderer);
SDL_RenderCopy(mRenderer, mPrimaryTexture, NULL, &destRect);
SDL_RenderPresent(mRenderer);
}
I am assuming that SDL has already been setup, this can all happen as usual. The important one is the SDL_JoystickUpdate (if you are using it) as it was called in the PollEvent function.
Game center and all other iOS windows will now work correctly.
Enjoy