Darrell added support for emulated SDL_DOUBLEBUF on MacOSX
authorSam Lantinga <slouken@libsdl.org>
Sat, 01 Feb 2003 19:59:23 +0000
changeset 5882c6510c0a304
parent 587 f00ccf8d8edc
child 589 2e58ece48b61
Darrell added support for emulated SDL_DOUBLEBUF on MacOSX
src/video/quartz/SDL_QuartzVideo.h
src/video/quartz/SDL_QuartzVideo.m
     1.1 --- a/src/video/quartz/SDL_QuartzVideo.h	Sat Feb 01 19:56:45 2003 +0000
     1.2 +++ b/src/video/quartz/SDL_QuartzVideo.h	Sat Feb 01 19:59:23 2003 +0000
     1.3 @@ -53,7 +53,9 @@
     1.4  #include <Carbon/Carbon.h>
     1.5  #include <QuickTime/QuickTime.h>
     1.6  #include <IOKit/IOKitLib.h>	/* For powersave handling */
     1.7 +#include <pthread.h>
     1.8  
     1.9 +#include "SDL_thread.h"
    1.10  #include "SDL_video.h"
    1.11  #include "SDL_error.h"
    1.12  #include "SDL_timer.h"
    1.13 @@ -137,6 +139,11 @@
    1.14      Uint8              grab_state;         /* used to manage grab behavior */
    1.15      NSPoint            cursor_loc;         /* saved cursor coords, for activate/deactivate when grabbed */
    1.16      BOOL          	   cursor_visible;     /* tells if cursor was hidden or not */
    1.17 +    Uint8*             sw_buffers[2];      /* pointers to the two software buffers for double-buffer emulation */
    1.18 +    SDL_Thread         *thread;            /* thread for async updates to the screen */
    1.19 +    SDL_sem            *sem1, *sem2;       /* synchronization for async screen updates */
    1.20 +    Uint8              *current_buffer;    /* the buffer being copied to the screen */
    1.21 +    BOOL               quit_thread;        /* used to quit the async blitting thread */
    1.22      
    1.23      ImageDescriptionHandle yuv_idh;
    1.24      MatrixRecordPtr        yuv_matrix;
    1.25 @@ -176,6 +183,12 @@
    1.26  #define grab_state (this->hidden->grab_state)
    1.27  #define cursor_loc (this->hidden->cursor_loc)
    1.28  #define cursor_visible (this->hidden->cursor_visible)
    1.29 +#define sw_buffers (this->hidden->sw_buffers)
    1.30 +#define thread (this->hidden->thread)
    1.31 +#define sem1 (this->hidden->sem1)
    1.32 +#define sem2 (this->hidden->sem2)
    1.33 +#define current_buffer (this->hidden->current_buffer)
    1.34 +#define quit_thread (this->hidden->quit_thread)
    1.35  
    1.36  #define yuv_idh (this->hidden->yuv_idh)
    1.37  #define yuv_matrix (this->hidden->yuv_matrix)
    1.38 @@ -262,6 +275,8 @@
    1.39  
    1.40  extern CGSError CGSGetMouseEnabledFlags (CGSConnectionID cid, CGSWindowID wid, int *flags);
    1.41  
    1.42 +int CGSDisplayHWSync (CGDirectDisplayID id);
    1.43 +
    1.44  /* Bootstrap functions */
    1.45  static int              QZ_Available ();
    1.46  static SDL_VideoDevice* QZ_CreateDevice (int device_index);
    1.47 @@ -280,6 +295,13 @@
    1.48  static int          QZ_ToggleFullScreen (_THIS, int on);
    1.49  static int          QZ_SetColors        (_THIS, int first_color,
    1.50                                           int num_colors, SDL_Color *colors);
    1.51 +
    1.52 +static int          QZ_LockDoubleBuffer   (_THIS, SDL_Surface *surface);
    1.53 +static void         QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface);
    1.54 +static int          QZ_ThreadFlip         (_THIS);
    1.55 +static int          QZ_FlipDoubleBuffer   (_THIS, SDL_Surface *surface);
    1.56 +static void         QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects);
    1.57 +
    1.58  static void         QZ_DirectUpdate     (_THIS, int num_rects, SDL_Rect *rects);
    1.59  static int          QZ_LockWindow       (_THIS, SDL_Surface *surface);
    1.60  static void         QZ_UnlockWindow     (_THIS, SDL_Surface *surface);
     2.1 --- a/src/video/quartz/SDL_QuartzVideo.m	Sat Feb 01 19:56:45 2003 +0000
     2.2 +++ b/src/video/quartz/SDL_QuartzVideo.m	Sat Feb 01 19:59:23 2003 +0000
     2.3 @@ -32,6 +32,7 @@
     2.4      "Quartz", "Mac OS X CoreGraphics", QZ_Available, QZ_CreateDevice
     2.5  };
     2.6  
     2.7 +
     2.8  /* Bootstrap functions */
     2.9  static int QZ_Available () {
    2.10      return 1;
    2.11 @@ -360,6 +361,16 @@
    2.12          
    2.13          gamma_error = QZ_FadeGammaOut (this, &gamma_table);
    2.14  
    2.15 +        /*  Release double buffer stuff */
    2.16 +        if ( mode_flags & (SDL_HWSURFACE|SDL_DOUBLEBUF)) {
    2.17 +            quit_thread = YES;
    2.18 +            SDL_SemPost (sem1);
    2.19 +            SDL_WaitThread (thread, NULL);
    2.20 +            SDL_DestroySemaphore (sem1);
    2.21 +            SDL_DestroySemaphore (sem2);
    2.22 +            free (sw_buffers[0]);
    2.23 +        }
    2.24 +        
    2.25          /* 
    2.26              Release the OpenGL context
    2.27              Do this first to avoid trash on the display before fade
    2.28 @@ -372,7 +383,7 @@
    2.29          
    2.30          /* Restore original screen resolution/bpp */
    2.31          CGDisplaySwitchToMode (display_id, save_mode);
    2.32 -        CGDisplayRelease (display_id);
    2.33 +        CGReleaseAllDisplays ();
    2.34          ShowMenuBar ();
    2.35  
    2.36          /* 
    2.37 @@ -408,6 +419,7 @@
    2.38      int gamma_error;
    2.39      SDL_QuartzGammaTable gamma_table;
    2.40      NSRect screen_rect;
    2.41 +    CGError error;
    2.42      
    2.43      /* Destroy any previous mode */
    2.44      if (video_set == SDL_TRUE)
    2.45 @@ -427,7 +439,12 @@
    2.46      gamma_error = QZ_FadeGammaOut (this, &gamma_table);
    2.47  
    2.48      /* Put up the blanking window (a window above all other windows) */
    2.49 -    if ( CGDisplayNoErr != CGDisplayCapture (display_id) ) {
    2.50 +    if (getenv ("SDL_SINGLEDISPLAY"))
    2.51 +        error = CGDisplayCapture (display_id);
    2.52 +    else
    2.53 +        error = CGCaptureAllDisplays ();
    2.54 +        
    2.55 +    if ( CGDisplayNoErr != error ) {
    2.56          SDL_SetError ("Failed capturing display");
    2.57          goto ERR_NO_CAPTURE;
    2.58      }
    2.59 @@ -451,11 +468,41 @@
    2.60      this->UpdateRects     = QZ_DirectUpdate;
    2.61      this->LockHWSurface   = QZ_LockHWSurface;
    2.62      this->UnlockHWSurface = QZ_UnlockHWSurface;
    2.63 -    
    2.64 -    /* Setup some mode-dependant info */
    2.65 -    if ( CGSDisplayCanHWFill (display_id) ) {
    2.66 -        this->info.blit_fill = 1;
    2.67 -        this->FillHWRect = QZ_FillHWRect;
    2.68 +
    2.69 +    /* Setup double-buffer emulation */
    2.70 +    if ( flags & SDL_DOUBLEBUF ) {
    2.71 +        
    2.72 +        /*
    2.73 +            Setup a software backing store for reasonable results when
    2.74 +            double buffering is requested (since a single-buffered hardware
    2.75 +            surface looks hideous).
    2.76 +            
    2.77 +            The actual screen blit occurs in a separate thread to allow 
    2.78 +            other blitting while waiting on the VBL (and hence results in higher framerates).
    2.79 +        */
    2.80 +        this->LockHWSurface = NULL;
    2.81 +        this->UnlockHWSurface = NULL;
    2.82 +        this->UpdateRects = NULL;
    2.83 +        
    2.84 +        current->flags |= (SDL_HWSURFACE|SDL_DOUBLEBUF);
    2.85 +        this->UpdateRects = QZ_DoubleBufferUpdate;
    2.86 +        this->LockHWSurface = QZ_LockDoubleBuffer;
    2.87 +        this->UnlockHWSurface = QZ_UnlockDoubleBuffer;
    2.88 +        this->FlipHWSurface = QZ_FlipDoubleBuffer;
    2.89 +
    2.90 +        current->pixels = malloc (current->pitch * current->h * 2);
    2.91 +        if (current->pixels == NULL) {
    2.92 +            SDL_OutOfMemory ();
    2.93 +            goto ERR_DOUBLEBUF;
    2.94 +        }
    2.95 +        
    2.96 +        sw_buffers[0] = current->pixels;
    2.97 +        sw_buffers[1] = (Uint8*)current->pixels + current->pitch * current->h;
    2.98 +        
    2.99 +        quit_thread = NO;
   2.100 +        sem1 = SDL_CreateSemaphore (0);
   2.101 +        sem2 = SDL_CreateSemaphore (1);
   2.102 +        thread = SDL_CreateThread ((int (*)(void *))QZ_ThreadFlip, this);
   2.103      }
   2.104  
   2.105      if ( CGDisplayCanSetPalette (display_id) )
   2.106 @@ -511,10 +558,11 @@
   2.107      return current;
   2.108  
   2.109      /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
   2.110 -ERR_NO_GL:      CGDisplaySwitchToMode (display_id, save_mode);
   2.111 -ERR_NO_SWITCH:  CGDisplayRelease (display_id);
   2.112 +ERR_NO_GL:      
   2.113 +ERR_DOUBLEBUF:  CGDisplaySwitchToMode (display_id, save_mode);
   2.114 +ERR_NO_SWITCH:  CGReleaseAllDisplays ();
   2.115  ERR_NO_CAPTURE: if (!gamma_error) { QZ_FadeGammaIn (this, &gamma_table); }
   2.116 -ERR_NO_MATCH:    return NULL;
   2.117 +ERR_NO_MATCH:   return NULL;
   2.118  }
   2.119  
   2.120  static SDL_Surface* QZ_SetVideoWindowed (_THIS, SDL_Surface *current, int width,
   2.121 @@ -723,6 +771,151 @@
   2.122      return 1;
   2.123  }
   2.124  
   2.125 +static int QZ_LockDoubleBuffer (_THIS, SDL_Surface *surface) {
   2.126 +
   2.127 +    return 1;
   2.128 +}
   2.129 +
   2.130 +static void QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface) {
   2.131 +
   2.132 +}
   2.133 +
   2.134 + /* The VBL delay is based on code by Ian R Ollmann's RezLib <iano@cco.caltech.edu> */
   2.135 + static AbsoluteTime QZ_SecondsToAbsolute ( double seconds ) {
   2.136 +    
   2.137 +    union
   2.138 +    {
   2.139 +        UInt64	i;
   2.140 +        Nanoseconds ns;
   2.141 +    } temp;
   2.142 +        
   2.143 +    temp.i = seconds * 1000000000.0;
   2.144 +    
   2.145 +    return NanosecondsToAbsolute ( temp.ns );
   2.146 +}
   2.147 +
   2.148 +static int QZ_ThreadFlip (_THIS) {
   2.149 +
   2.150 +    Uint8 *src, *dst;
   2.151 +    int skip, len, h;
   2.152 +    
   2.153 +    /*
   2.154 +        Give this thread the highest scheduling priority possible,
   2.155 +        in the hopes that it will immediately run after the VBL delay
   2.156 +    */
   2.157 +    {
   2.158 +        pthread_t current_thread;
   2.159 +        int policy;
   2.160 +        struct sched_param param;
   2.161 +        
   2.162 +        current_thread = pthread_self ();
   2.163 +        pthread_getschedparam (current_thread, &policy, &param);
   2.164 +        policy = SCHED_RR;
   2.165 +        param.sched_priority = sched_get_priority_max (policy);
   2.166 +        pthread_setschedparam (current_thread, policy, &param);
   2.167 +    }
   2.168 +    
   2.169 +    while (1) {
   2.170 +    
   2.171 +        SDL_SemWait (sem1);
   2.172 +        if (quit_thread)
   2.173 +            return 0;
   2.174 +                
   2.175 +        dst = CGDisplayBaseAddress (display_id);
   2.176 +        src = current_buffer;
   2.177 +        len = SDL_VideoSurface->w * SDL_VideoSurface->format->BytesPerPixel;
   2.178 +        h = SDL_VideoSurface->h;
   2.179 +        skip = SDL_VideoSurface->pitch;
   2.180 +    
   2.181 +        /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */
   2.182 +        {
   2.183 +            
   2.184 +            /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */
   2.185 +            double refreshRate;
   2.186 +            double linesPerSecond;
   2.187 +            double target;
   2.188 +            double position;
   2.189 +            double adjustment;
   2.190 +            AbsoluteTime nextTime;        
   2.191 +            CFNumberRef refreshRateCFNumber;
   2.192 +            
   2.193 +            refreshRateCFNumber = CFDictionaryGetValue (mode, kCGDisplayRefreshRate);
   2.194 +            if ( NULL == refreshRateCFNumber ) {
   2.195 +                SDL_SetError ("Mode has no refresh rate");
   2.196 +                goto ERROR;
   2.197 +            }
   2.198 +            
   2.199 +            if ( 0 == CFNumberGetValue (refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) ) {
   2.200 +                SDL_SetError ("Error getting refresh rate");
   2.201 +                goto ERROR;
   2.202 +            }
   2.203 +            
   2.204 +            if ( 0 == refreshRate ) {
   2.205 +               
   2.206 +               SDL_SetError ("Display has no refresh rate, using 60hz");
   2.207 +                
   2.208 +                /* ok, for LCD's we'll emulate a 60hz refresh, which may or may not look right */
   2.209 +                refreshRate = 60.0;
   2.210 +            }
   2.211 +            
   2.212 +            linesPerSecond = refreshRate * h;
   2.213 +            target = h;
   2.214 +        
   2.215 +            /* Figure out the first delay so we start off about right */
   2.216 +            position = CGDisplayBeamPosition (display_id);
   2.217 +            if (position > target)
   2.218 +                position = 0;
   2.219 +            
   2.220 +            adjustment = (target - position) / linesPerSecond; 
   2.221 +            
   2.222 +            nextTime = AddAbsoluteToAbsolute (UpTime (), QZ_SecondsToAbsolute (adjustment));
   2.223 +        
   2.224 +            MPDelayUntil (&nextTime);
   2.225 +        }
   2.226 +        
   2.227 +        
   2.228 +        /* On error, skip VBL delay */
   2.229 +        ERROR:
   2.230 +        
   2.231 +        while ( h-- ) {
   2.232 +        
   2.233 +            memcpy (dst, src, len);
   2.234 +            src += skip;
   2.235 +            dst += skip;
   2.236 +        }
   2.237 +        
   2.238 +        /* signal flip completion */
   2.239 +        SDL_SemPost (sem2);
   2.240 +    }
   2.241 +    
   2.242 +    return 0;
   2.243 +}
   2.244 +        
   2.245 +static int QZ_FlipDoubleBuffer (_THIS, SDL_Surface *surface) {
   2.246 +
   2.247 +    /* wait for previous flip to complete */
   2.248 +    SDL_SemWait (sem2);
   2.249 +    
   2.250 +    current_buffer = surface->pixels;
   2.251 +        
   2.252 +    if (surface->pixels == sw_buffers[0])
   2.253 +        surface->pixels = sw_buffers[1];
   2.254 +    else
   2.255 +        surface->pixels = sw_buffers[0];
   2.256 +    
   2.257 +    /* signal worker thread to do the flip */
   2.258 +    SDL_SemPost (sem1);
   2.259 +    
   2.260 +    return 0;
   2.261 +}
   2.262 +
   2.263 +
   2.264 +static void QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects) {
   2.265 +
   2.266 +    /* perform a flip if someone calls updaterects on a doublebuferred surface */
   2.267 +    this->FlipHWSurface (this, SDL_VideoSurface);
   2.268 +}
   2.269 +
   2.270  static void QZ_DirectUpdate (_THIS, int num_rects, SDL_Rect *rects) {
   2.271  #pragma unused(this,num_rects,rects)
   2.272  }