(Said Max Horn on the SDL mailing list...)
Hi folks,
based on Eric Wing's patch, I created the attached patch which fixes
the OpenGL coordinate inversion bug in SDL. It works fine over here on
10.3 with Ryan's test program (which I also attached).
There is another change in it: I removed the "- 1" in the two lines
using CGDisplayPixelsHigh()... while I understand from a logical point
of view why they *should* be correct, I checked the actual values
computed that way, and they were off-by-one. After removing the " - 1",
the returned mouse coordinates are correct. I checked this by moving
the mouse to the screen top/bottom in fullscreen mode, BTW. With the
change, the proper values 0 and 479 are returned (in 640x480 mode).
Sam, you may still want to test on 10.1, it's very simple using Ryan's
minimal test code :-)
Cheers,
Max
(Here is the reproduction case for revision history's sake...)
/*
* To compile:
* gcc -o test test.c `sdl-config --cflags` `sdl-config --libs` -framework OpenGL
*
* --ryan.
*/
#include <stdio.h>
#include "SDL.h"
#include "SDL_opengl.h"
int main(int argc, char **argv)
{
Uint32 flags = SDL_OPENGL /* | SDL_FULLSCREEN */;
SDL_Surface *screen;
SDL_Event event;
int done = 0;
GLfloat ratio;
SDL_Init(SDL_INIT_VIDEO);
SDL_ShowCursor(0);
if ((argv[1]) && (strcmp(argv[1], "--grab") == 0))
SDL_WM_GrabInput(SDL_GRAB_ON);
screen = SDL_SetVideoMode(640, 480, 0, flags);
if (!screen)
return(42);
ratio = ((GLfloat) screen->w) / ((GLfloat) screen->h);
glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
glClearDepth( 1.0f );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glViewport( 0, 0, screen->w, screen->h);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 45.0f, ratio, 0.1f, 100.0f );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SDL_GL_SwapBuffers();
// eh, close enough.
#define MAX_X 6.12
#define MAX_Y 4.50
while (!done)
{
int x, y;
GLfloat glx, gly;
if (!SDL_WaitEvent(&event))
break;
switch (event.type)
{
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_ESCAPE)
done = 1;
break;
}
SDL_GetMouseState(&x, &y);
glx = ((((GLfloat) x) / ((GLfloat) screen->w)) - 0.5f) * MAX_X;
gly = ((((GLfloat) y) / ((GLfloat) screen->h)) - 0.5f) * MAX_Y;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(glx,-gly,-6.0f);
glBegin(GL_TRIANGLES);
glColor3f(1,0,0); glVertex3f( 0.00f, 0.25f, 0.00f);
glColor3f(0,1,0); glVertex3f(-0.25f, -0.25f, 0.00f);
glColor3f(0,0,1); glVertex3f( 0.25f, -0.25f, 0.00f);
glEnd();
SDL_GL_SwapBuffers();
}
SDL_Quit();
return(0);
}
/* end of test.c ... */
/*
SDL - Simple DirectMedia Layer
Copyright (C) 1997-2003 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
slouken@libsdl.org
*/
#include "SDL_QuartzVideo.h"
struct WMcursor {
Cursor curs;
};
void QZ_FreeWMCursor (_THIS, WMcursor *cursor) {
if ( cursor != NULL )
free (cursor);
}
/* Use the Carbon cursor routines for now */
WMcursor* QZ_CreateWMCursor (_THIS, Uint8 *data, Uint8 *mask,
int w, int h, int hot_x, int hot_y) {
WMcursor *cursor;
int row, bytes;
/* Allocate the cursor memory */
cursor = (WMcursor *)malloc(sizeof(WMcursor));
if ( cursor == NULL ) {
SDL_OutOfMemory();
return(NULL);
}
memset(cursor, 0, sizeof(*cursor));
if (w > 16)
w = 16;
if (h > 16)
h = 16;
bytes = (w+7)/8;
for ( row=0; row<h; ++row ) {
memcpy(&cursor->curs.data[row], data, bytes);
data += bytes;
}
for ( row=0; row<h; ++row ) {
memcpy(&cursor->curs.mask[row], mask, bytes);
mask += bytes;
}
cursor->curs.hotSpot.h = hot_x;
cursor->curs.hotSpot.v = hot_y;
return(cursor);
}
void QZ_ShowMouse (_THIS) {
if (!cursor_visible) {
[ NSCursor unhide ];
cursor_visible = YES;
}
}
void QZ_HideMouse (_THIS) {
BOOL isInGameWin = QZ_IsMouseInWindow (this);
if (isInGameWin && cursor_visible) {
[ NSCursor hide ];
cursor_visible = NO;
}
}
BOOL QZ_IsMouseInWindow (_THIS) {
return (mode_flags & SDL_FULLSCREEN) ? true : NSPointInRect([ qz_window mouseLocationOutsideOfEventStream ], [ window_view frame ]);
}
int QZ_ShowWMCursor (_THIS, WMcursor *cursor) {
if ( cursor == NULL) {
if ( cursor_should_be_visible ) {
QZ_HideMouse (this);
cursor_should_be_visible = NO;
QZ_ChangeGrabState (this, QZ_HIDECURSOR);
}
}
else {
SetCursor(&cursor->curs);
if ( ! cursor_should_be_visible ) {
QZ_ShowMouse (this);
cursor_should_be_visible = YES;
QZ_ChangeGrabState (this, QZ_SHOWCURSOR);
}
}
return 1;
}
/*
Coordinate conversion functions, for convenience
Cocoa sets the origin at the lower left corner of the window/screen
SDL, CoreGraphics/WindowServer, and QuickDraw use the origin at the upper left corner
The routines were written so they could be called before SetVideoMode() has finished;
this might have limited usefulness at the moment, but the extra cost is trivial.
*/
/* Convert Cocoa screen coordinate to Cocoa window coordinate */
void QZ_PrivateGlobalToLocal (_THIS, NSPoint *p) {
*p = [ qz_window convertScreenToBase:*p ];
}
/* Convert Cocoa window coordinate to Cocoa screen coordinate */
void QZ_PrivateLocalToGlobal (_THIS, NSPoint *p) {
*p = [ qz_window convertBaseToScreen:*p ];
}
/* Convert SDL coordinate to Cocoa coordinate */
void QZ_PrivateSDLToCocoa (_THIS, NSPoint *p) {
if ( CGDisplayIsCaptured (display_id) ) { /* capture signals fullscreen */
p->y = CGDisplayPixelsHigh (display_id) - p->y;
}
else {
NSPoint newPoint;
newPoint = [ window_view convertPoint:*p toView:[ qz_window contentView ] ];
*p = newPoint;
}
}
/* Convert Cocoa coordinate to SDL coordinate */
void QZ_PrivateCocoaToSDL (_THIS, NSPoint *p) {
if ( CGDisplayIsCaptured (display_id) ) { /* capture signals fullscreen */
p->y = CGDisplayPixelsHigh (display_id) - p->y;
}
else {
NSPoint newPoint;
newPoint = [ window_view convertPoint:*p fromView:[ qz_window contentView ] ];
*p = newPoint;
/* If OSX version is 10.3.0 or later, we need a workaround in OpenGL mode */
if( system_version >= 0x1030 && (SDL_VideoSurface->flags & (SDL_OPENGL | SDL_OPENGLBLIT)) ) {
p->y = [window_view frame].size.height - p->y - 1;
}
}
}
/* Convert SDL coordinate to window server (CoreGraphics) coordinate */
CGPoint QZ_PrivateSDLToCG (_THIS, NSPoint *p) {
CGPoint cgp;
if ( ! CGDisplayIsCaptured (display_id) ) { /* not captured => not fullscreen => local coord */
int height;
QZ_PrivateSDLToCocoa (this, p);
QZ_PrivateLocalToGlobal (this, p);
height = CGDisplayPixelsHigh (display_id);
p->y = height - p->y;
}
cgp.x = p->x;
cgp.y = p->y;
return cgp;
}
#if 0 /* Dead code */
/* Convert window server (CoreGraphics) coordinate to SDL coordinate */
void QZ_PrivateCGToSDL (_THIS, NSPoint *p) {
if ( ! CGDisplayIsCaptured (display_id) ) { /* not captured => not fullscreen => local coord */
int height;
/* Convert CG Global to Cocoa Global */
height = CGDisplayPixelsHigh (display_id);
p->y = height - p->y;
QZ_PrivateGlobalToLocal (this, p);
QZ_PrivateCocoaToSDL (this, p);
}
}
#endif /* Dead code */
void QZ_PrivateWarpCursor (_THIS, int x, int y) {
NSPoint p;
CGPoint cgp;
p = NSMakePoint (x, y);
cgp = QZ_PrivateSDLToCG (this, &p);
/* this is the magic call that fixes cursor "freezing" after warp */
CGSetLocalEventsSuppressionInterval (0.0);
CGWarpMouseCursorPosition (cgp);
}
void QZ_WarpWMCursor (_THIS, Uint16 x, Uint16 y) {
/* Only allow warping when in foreground */
if ( ! [ NSApp isActive ] )
return;
/* Do the actual warp */
QZ_PrivateWarpCursor (this, x, y);
/* Generate the mouse moved event */
SDL_PrivateMouseMotion (0, 0, x, y);
}
void QZ_MoveWMCursor (_THIS, int x, int y) { }
void QZ_CheckMouseMode (_THIS) { }
void QZ_SetCaption (_THIS, const char *title, const char *icon) {
if ( qz_window != nil ) {
NSString *string;
if ( title != NULL ) {
string = [ [ NSString alloc ] initWithCString:title ];
[ qz_window setTitle:string ];
[ string release ];
}
if ( icon != NULL ) {
string = [ [ NSString alloc ] initWithCString:icon ];
[ qz_window setMiniwindowTitle:string ];
[ string release ];
}
}
}
void QZ_SetIcon (_THIS, SDL_Surface *icon, Uint8 *mask)
{
NSBitmapImageRep *imgrep;
NSImage *img;
SDL_Surface *mergedSurface;
int i,j;
NSAutoreleasePool *pool;
SDL_Rect rrect;
NSSize imgSize = {icon->w, icon->h};
pool = [ [ NSAutoreleasePool alloc ] init ];
SDL_GetClipRect(icon, &rrect);
/* create a big endian RGBA surface */
mergedSurface = SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,
icon->w, icon->h, 32, 0xff<<24, 0xff<<16, 0xff<<8, 0xff<<0);
if (mergedSurface==NULL) {
NSLog(@"Error creating surface for merge");
goto freePool;
}
if (mergedSurface->pitch !=
mergedSurface->format->BytesPerPixel * mergedSurface->w) {
SDL_SetError ("merged surface has wrong format");
SDL_FreeSurface (mergedSurface);
goto freePool;
}
if (SDL_BlitSurface(icon,&rrect,mergedSurface,&rrect)) {
NSLog(@"Error blitting to mergedSurface");
goto freePool;
}
if (mask) {
Uint32 *pixels = mergedSurface->pixels;
for (i = 0; i < mergedSurface->h; i++) {
for (j = 0; j < mergedSurface->w; j++) {
int index = i * mergedSurface->w + j;
int mindex = index >> 3;
int bindex = 7 - (index & 0x7);
if (mask[mindex] & (1 << bindex))
pixels[index] |= 0x000000FF;
else
pixels[index] &= 0xFFFFFF00;
}
}
}
imgrep = [ [ NSBitmapImageRep alloc]
initWithBitmapDataPlanes:(unsigned char **)&mergedSurface->pixels
pixelsWide:icon->w pixelsHigh:icon->h bitsPerSample:8 samplesPerPixel:4
hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:icon->w<<2 bitsPerPixel:32 ];
img = [ [ NSImage alloc ] initWithSize:imgSize ];
[ img addRepresentation: imgrep ];
[ NSApp setApplicationIconImage:img ];
[ img release ];
[ imgrep release ];
SDL_FreeSurface(mergedSurface);
freePool:
[pool release];
}
int QZ_IconifyWindow (_THIS) {
if ( ! [ qz_window isMiniaturized ] ) {
[ qz_window miniaturize:nil ];
return 1;
}
else {
SDL_SetError ("window already iconified");
return 0;
}
}
/*
int QZ_GetWMInfo (_THIS, SDL_SysWMinfo *info) {
info->nsWindowPtr = qz_window;
return 0;
}*/
void QZ_ChangeGrabState (_THIS, int action) {
/*
Figure out what the next state should be based on the action.
Ignore actions that can't change the current state.
*/
if ( grab_state == QZ_UNGRABBED ) {
if ( action == QZ_ENABLE_GRAB ) {
if ( cursor_should_be_visible )
grab_state = QZ_VISIBLE_GRAB;
else
grab_state = QZ_INVISIBLE_GRAB;
}
}
else if ( grab_state == QZ_VISIBLE_GRAB ) {
if ( action == QZ_DISABLE_GRAB )
grab_state = QZ_UNGRABBED;
else if ( action == QZ_HIDECURSOR )
grab_state = QZ_INVISIBLE_GRAB;
}
else {
assert( grab_state == QZ_INVISIBLE_GRAB );
if ( action == QZ_DISABLE_GRAB )
grab_state = QZ_UNGRABBED;
else if ( action == QZ_SHOWCURSOR )
grab_state = QZ_VISIBLE_GRAB;
}
/* now apply the new state */
if (grab_state == QZ_UNGRABBED) {
CGAssociateMouseAndMouseCursorPosition (1);
}
else if (grab_state == QZ_VISIBLE_GRAB) {
CGAssociateMouseAndMouseCursorPosition (1);
}
else {
assert( grab_state == QZ_INVISIBLE_GRAB );
QZ_PrivateWarpCursor (this, SDL_VideoSurface->w / 2, SDL_VideoSurface->h / 2);
CGAssociateMouseAndMouseCursorPosition (0);
}
}
SDL_GrabMode QZ_GrabInput (_THIS, SDL_GrabMode grab_mode) {
int doGrab = grab_mode & SDL_GRAB_ON;
/*int fullscreen = grab_mode & SDL_GRAB_FULLSCREEN;*/
if ( this->screen == NULL ) {
SDL_SetError ("QZ_GrabInput: screen is NULL");
return SDL_GRAB_OFF;
}
if ( ! video_set ) {
/*SDL_SetError ("QZ_GrabInput: video is not set, grab will take effect on mode switch"); */
current_grab_mode = grab_mode;
return grab_mode; /* Will be set later on mode switch */
}
if ( grab_mode != SDL_GRAB_QUERY ) {
if ( doGrab )
QZ_ChangeGrabState (this, QZ_ENABLE_GRAB);
else
QZ_ChangeGrabState (this, QZ_DISABLE_GRAB);
current_grab_mode = doGrab ? SDL_GRAB_ON : SDL_GRAB_OFF;
}
return current_grab_mode;
}