diff src/video/x11/SDL_x11modes.c @ 2873:b33e38aaa027

Progress on fullscreen mode switching on X11
author Sam Lantinga <slouken@libsdl.org>
date Tue, 16 Dec 2008 17:41:03 +0000
parents b801df19835f
children 36e312e0fac0
line wrap: on
line diff
--- a/src/video/x11/SDL_x11modes.c	Tue Dec 16 17:40:30 2008 +0000
+++ b/src/video/x11/SDL_x11modes.c	Tue Dec 16 17:41:03 2008 +0000
@@ -23,6 +23,10 @@
 
 #include "SDL_x11video.h"
 
+//#define X11MODES_DEBUG
+#undef SDL_VIDEO_DRIVER_X11_XINERAMA
+#undef SDL_VIDEO_DRIVER_X11_XRANDR
+#undef SDL_VIDEO_DRIVER_X11_VIDMODE
 
 static int
 get_visualinfo(Display * display, int screen, XVisualInfo * vinfo)
@@ -153,20 +157,487 @@
     }
 }
 
+/* Global for the error handler */
+int vm_event, vm_error = -1;
+
+#if SDL_VIDEO_DRIVER_X11_XINERAMA
+static SDL_bool
+CheckXinerama(Display *display, int *major, int *minor)
+{
+    const char *env;
+
+    /* Default the extension not available */
+    *major = *minor = 0;
+
+    /* Allow environment override */
+    env = getenv("SDL_VIDEO_X11_XINERAMA");
+    if (env && !SDL_atoi(env)) {
+        return SDL_FALSE;
+    }
+
+    /* Query the extension version */
+    if (!SDL_NAME(XineramaQueryExtension)(display, major, minor) ||
+        !SDL_NAME(XineramaIsActive)(display)) {
+        return SDL_FALSE;
+    }
+    return SDL_TRUE;
+}
+#endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */
+
+#if SDL_VIDEO_DRIVER_X11_XRANDR
+static SDL_bool
+CheckXRandR(Display *display, int *major, int *minor)
+{
+    const char *env;
+
+    /* Default the extension not available */
+    *major = *minor = 0;
+
+    /* Allow environment override */
+    env = getenv("SDL_VIDEO_X11_XRANDR");
+    if (env && !SDL_atoi(env)) {
+        return SDL_FALSE;
+    }
+
+    if (!SDL_X11_HAVE_XRANDR) {
+        return SDL_FALSE;
+    }
+
+    /* Query the extension version */
+    if (!XRRQueryVersion(display, major, minor)) {
+        return SDL_FALSE;
+    }
+    return SDL_TRUE;
+}
+#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
+
+#if SDL_VIDEO_DRIVER_X11_VIDMODE
+static SDL_bool
+CheckVidMode(Display *display, int *major, int *minor)
+{
+    const char *env;
+
+    /* Default the extension not available */
+    *major = *minor = 0;
+
+    /* Allow environment override */
+    env = getenv("SDL_VIDEO_X11_VIDMODE");
+    if (env && !SDL_atoi(env)) {
+        return SDL_FALSE;
+    }
+    
+    /* Query the extension version */
+    vm_error = -1;
+    if (!SDL_NAME(XF86VidModeQueryExtension)(display, &vm_event, &vm_error) ||
+        !SDL_NAME(XF86VidModeQueryVersion)(display, major, minor)) {
+        return SDL_FALSE;
+    }
+    return SDL_TRUE;
+}
+
+Bool SDL_NAME(XF86VidModeGetModeInfo)(Display *dpy, int scr, SDL_NAME(XF86VidModeModeInfo) *info)
+{
+    Bool retval;
+    int dotclock;
+    SDL_NAME(XF86VidModeModeLine) l;
+    SDL_zerop(info);
+    SDL_zero(l);
+    retval = SDL_NAME(XF86VidModeGetModeLine)(dpy, scr, &dotclock, &l);
+    info->dotclock = dotclock;
+    info->hdisplay = l.hdisplay;
+    info->hsyncstart = l.hsyncstart;
+    info->hsyncend = l.hsyncend;
+    info->htotal = l.htotal;
+    info->hskew = l.hskew;
+    info->vdisplay = l.vdisplay;
+    info->vsyncstart = l.vsyncstart;
+    info->vsyncend = l.vsyncend;
+    info->vtotal = l.vtotal;
+    info->flags = l.flags;
+    info->privsize = l.privsize;
+    info->private = l.private;
+    return retval;
+}
+
+static int
+calculate_rate(SDL_NAME(XF86VidModeModeInfo) *info)
+{
+    return (info->htotal && info->vtotal) ? (1000 * info->dotclock / (info->htotal * info->vtotal)) : 0;
+}
+
+static void
+save_mode(Display *display, SDL_DisplayData *data)
+{
+    SDL_NAME(XF86VidModeGetModeInfo)(display, data->screen, &data->saved_mode);
+    SDL_NAME(XF86VidModeGetViewPort)(display, data->screen, &data->saved_view.x,&data->saved_view.y);
+}
+
+static void
+restore_mode(Display *display, SDL_DisplayData *data)
+{
+    SDL_NAME(XF86VidModeModeInfo) mode;
+
+    if (SDL_NAME(XF86VidModeGetModeInfo)(display, data->screen, &mode)) {
+        if (SDL_memcmp(&mode, &data->saved_mode, sizeof(mode)) != 0) {
+            SDL_NAME(XF86VidModeSwitchToMode)(display, data->screen, &data->saved_mode);
+        }
+    }
+    if ((data->saved_view.x != 0) || (data->saved_view.y != 0)) {
+        SDL_NAME(XF86VidModeSetViewPort)(display, data->screen, data->saved_view.x, data->saved_view.y);
+    }
+}
+#endif /* SDL_VIDEO_DRIVER_X11_VIDMODE */
+
 void
 X11_GetDisplayModes(_THIS)
 {
     Display *display = ((SDL_VideoData *) _this->driverdata)->display;
     SDL_DisplayData *data = (SDL_DisplayData *) SDL_CurrentDisplay.driverdata;
+#if SDL_VIDEO_DRIVER_X11_XINERAMA
+    int xinerama_major, xinerama_minor;
+    int screens;
+    SDL_NAME(XineramaScreenInfo) *xinerama;
+#endif
+#if SDL_VIDEO_DRIVER_X11_XRANDR
+    int xrandr_major, xrandr_minor;
+    int nsizes, nrates;
+    XRRScreenSize *sizes;
+    short *rates;
+#endif
+#if SDL_VIDEO_DRIVER_X11_VIDMODE
+    int vm_major, vm_minor;
+    int nmodes;
+    SDL_NAME(XF86VidModeModeInfo) **modes;
+#endif
+    int screen_w;
+    int screen_h;
     SDL_DisplayMode mode;
 
+    /* Unfortunately X11 requires the window to be created with the correct
+     * visual and depth ahead of time, but the SDL API allows you to create
+     * a window before setting the fullscreen display mode.  This means that
+     * we have to use the same format for all windows and all display modes.
+     * (or support recreating the window with a new visual behind the scenes)
+     */
+    mode.format = SDL_CurrentDisplay.current_mode.format;
+    mode.driverdata = NULL;
+
+    data->use_xinerama = 0;
+    data->use_xrandr = 0;
+    data->use_vidmode = 0;
+    screen_w = DisplayWidth(display, data->screen);
+    screen_h = DisplayHeight(display, data->screen);
+
+#if SDL_VIDEO_DRIVER_X11_XINERAMA
+    /* Query Xinerama extention */
+    if (CheckXinerama(display, &xinerama_major, &xinerama_minor)) {
+#ifdef X11MODES_DEBUG
+        printf("X11 detected Xinerama:\n");
+#endif
+        xinerama = SDL_NAME(XineramaQueryScreens)(display, &screens);
+        if (xinerama) {
+            int i;
+            for (i = 0; i < screens; i++) {
+#ifdef X11MODES_DEBUG
+                printf("xinerama %d: %dx%d+%d+%d\n",
+                    xinerama[i].screen_number,
+                    xinerama[i].width, xinerama[i].height,
+                    xinerama[i].x_org, xinerama[i].y_org);
+#endif
+                if (xinerama[i].screen_number == data->screen) {
+                    data->use_xinerama = xinerama_major * 100 + xinerama_minor;
+                    data->xinerama_info = xinerama[i];
+                }
+            }
+            XFree(xinerama);
+        }
+
+        if (data->use_xinerama) {
+            /* Add the full xinerama mode */
+            if (screen_w > data->xinerama_info.width ||
+                screen_h > data->xinerama_info.height) {
+                mode.w = screen_w;
+                mode.h = screen_h;
+                mode.refresh_rate = 0;
+                SDL_AddDisplayMode(_this->current_display, &mode);
+            }
+
+            /* Add the head xinerama mode */
+            mode.w = data->xinerama_info.width;
+            mode.h = data->xinerama_info.height;
+            mode.refresh_rate = 0;
+            SDL_AddDisplayMode(_this->current_display, &mode);
+        }
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */
+
+#if SDL_VIDEO_DRIVER_X11_XRANDR
+    /* XRandR */
+    /* require at least XRandR v1.0 (arbitrary) */
+    if (CheckXRandR(display, &xrandr_major, &xrandr_minor) && xrandr_major >= 1) {
+#ifdef X11MODES_DEBUG
+        fprintf(stderr, "XRANDR: XRRQueryVersion: V%d.%d\n",
+                xrandr_major, xrandr_minor);
+#endif
+
+        /* save the screen configuration since we must reference it
+           each time we toggle modes.
+        */
+        data->screen_config = XRRGetScreenInfo(display, RootWindow(display, data->screen));
+
+        /* retrieve the list of resolution */
+        sizes = XRRConfigSizes(data->screen_config, &nsizes);
+        if (nsizes > 0) {
+            int i, j;
+            for ( i=0; i < nsizes; i++) {
+                mode.w = sizes[i].width;
+                mode.h = sizes[i].height;
+
+                rates = XRRConfigRates(data->screen_config, i, &nrates);
+                for (j = 0; j < nrates; ++j) {
+                    mode.refresh_rate = rates[j];
+#ifdef X11MODES_DEBUG
+                    fprintf(stderr, "XRANDR: mode = %4d[%d], w = %4d, h = %4d, rate = %4d\n",
+                            i, j, mode.w, mode.h, mode.refresh_rate);
+#endif
+                    SDL_AddDisplayMode(_this->current_display, &mode);
+                }
+            }
+
+            data->use_xrandr = xrandr_major * 100 + xrandr_minor;
+            data->saved_size = XRRConfigCurrentConfiguration(data->screen_config, &data->saved_rotation);
+            data->saved_rate = XRRConfigCurrentRate(data->screen_config);
+        }
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
+
+#if SDL_VIDEO_DRIVER_X11_VIDMODE
+    /* XVidMode */
+    if (!data->use_xrandr &&
+#if SDL_VIDEO_DRIVER_X11_XINERAMA
+        (!data->use_xinerama || data->xinerama_info.screen_number == 0) &&
+#endif
+        CheckVidMode(display, &vm_major, &vm_minor) &&
+        SDL_NAME(XF86VidModeGetAllModeLines)(display, data->screen, &nmodes, &modes) )
+    {
+        int i;
+
+#ifdef X11MODES_DEBUG
+        printf("VidMode modes: (unsorted)\n");
+        for (i = 0; i < nmodes; ++i) {
+            printf("Mode %d: %d x %d @ %d\n", i,
+                    modes[i]->hdisplay, modes[i]->vdisplay,
+                    calculate_rate(modes[i]));
+        }
+#endif
+        for (i = 0; i < nmodes; ++i) {
+            mode.w = modes[i]->hdisplay;
+            mode.h = modes[i]->vdisplay;
+            mode.refresh_rate = calculate_rate(modes[i]);
+            SDL_AddDisplayMode(_this->current_display, &mode);
+        }
+        XFree(modes);
+
+        data->use_vidmode = vm_major * 100 + vm_minor;
+        save_mode(display, data);
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_VIDMODE */
+
+    if (!data->use_xrandr && !data->use_vidmode) {
+        mode.w = screen_w;
+        mode.h = screen_h;
+        mode.refresh_rate = 0;
+        SDL_AddDisplayMode(_this->current_display, &mode);
+    } 
+
+#ifdef X11MODES_DEBUG
+    if (data->use_xinerama) {
+        printf("Xinerama is enabled\n");
+    }
+
+    if (data->use_xrandr) {
+        printf("XRandR is enabled\n");
+    }
+
+    if (data->use_vidmode) {
+        printf("VidMode is enabled\n");
+    }
+#endif /* X11MODES_DEBUG */
+}
+
+static void
+get_real_resolution(Display *display, SDL_DisplayData *data, int *w, int *h, int *rate)
+{
+#if SDL_VIDEO_DRIVER_X11_XRANDR
+    if (data->use_xrandr) {
+        int nsizes;
+        XRRScreenSize *sizes;
+        
+        sizes = XRRConfigSizes(data->screen_config, &nsizes);
+        if (nsizes > 0) {
+            int cur_size;
+            Rotation cur_rotation;
+
+            cur_size = XRRConfigCurrentConfiguration(data->screen_config, &cur_rotation);
+            *w = sizes[cur_size].width;
+            *h = sizes[cur_size].height;
+            *rate = XRRConfigCurrentRate(data->screen_config);
+#ifdef X11MODES_DEBUG
+            fprintf(stderr, "XRANDR: get_real_resolution: w = %d, h = %d, rate = %d\n", *w, *h, *rate);
+#endif
+            return;
+        }
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
+
+#if SDL_VIDEO_DRIVER_X11_VIDMODE
+    if (data->use_vidmode) {
+        SDL_NAME(XF86VidModeModeInfo) mode;
+
+        if (SDL_NAME(XF86VidModeGetModeInfo)(display, data->screen, &mode)) {
+            *w = mode.hdisplay;
+            *h = mode.vdisplay;
+            *rate = calculate_rate(&mode);
+            return;
+        }
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_VIDMODE */
+
+#if SDL_VIDEO_DRIVER_X11_XINERAMA
+    if (data->use_xinerama) {
+        *w = data->xinerama_info.width;
+        *h = data->xinerama_info.height;
+        *rate = 0;
+        return;
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */
+
+    *w = DisplayWidth(display, data->screen);
+    *h = DisplayHeight(display, data->screen);
+    *rate = 0;
+}
+
+static void
+set_best_resolution(Display *display, SDL_DisplayData *data, int w, int h, int rate)
+{
+    int real_w, real_h, real_rate;
+
+    /* check current mode so we can avoid uneccessary mode changes */
+    get_real_resolution(display, data, &real_w, &real_h, &real_rate);
+    if (w == real_w && h == real_h && (!rate || rate == real_rate)) {
+        return;
+    }
+
+#if SDL_VIDEO_DRIVER_X11_XRANDR
+    if (data->use_xrandr) {
+#ifdef X11MODES_DEBUG
+        fprintf(stderr, "XRANDR: set_best_resolution(): w = %d, h = %d\n",
+                w, h);
+#endif
+        int i, nsizes, nrates;
+        int best;
+        int best_rate;
+        XRRScreenSize *sizes;
+        short *rates;
+
+        /* find the smallest resolution that is at least as big as the user requested */
+        best = -1;
+        sizes = XRRConfigSizes(data->screen_config, &nsizes);
+        for (i = 0; i < nsizes; ++i) {
+            if (sizes[i].width < w || sizes[i].height < h) {
+                continue;
+            }
+            if (sizes[i].width == w && sizes[i].height == h) {
+                best = i;
+                break;
+            }
+            if (best == -1 ||
+                (sizes[i].width < sizes[best].width) ||
+                (sizes[i].width == sizes[best].width && sizes[i].height < sizes[best].height)) {
+                best = i;
+            }
+        }
+
+        if (best >= 0) {
+            best_rate = 0;
+            rates = XRRConfigRates(data->screen_config, best, &nrates);
+            for (i = 0; i < nrates; ++i) {
+                if (rates[i] == rate) {
+                    best_rate = rate;
+                    break;
+                }
+                if (!rate) {
+                    /* Higher is better, right? */
+                    if (rates[i] > best_rate) {
+                        best_rate = rates[i];
+                    }
+                } else {
+                    if (SDL_abs(rates[i]-rate) < SDL_abs(best_rate-rate)) {
+                        best_rate = rates[i];
+                    }
+                }
+            }
+            XRRSetScreenConfigAndRate(display, data->screen_config, RootWindow(display, data->screen), best, data->saved_rotation, best_rate, CurrentTime);
+        }
+        return;
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
+
+#if SDL_VIDEO_DRIVER_X11_VIDMODE
+    if (data->use_vidmode) {
+        SDL_NAME(XF86VidModeModeInfo) **modes;
+        int i, nmodes;
+        int best;
+
+        if (SDL_NAME(XF86VidModeGetAllModeLines)(display,data->screen,&nmodes,&modes)) {
+            best = -1;
+            for (i = 0; i < nmodes; ++i) {
+                if (modes[i]->hdisplay < w || modes[i]->vdisplay < h) {
+                    continue;
+                }
+                if (best == -1 ||
+                    (modes[i]->hdisplay < modes[best]->hdisplay) ||
+                    (modes[i]->hdisplay == modes[best]->hdisplay && modes[i]->vdisplay < modes[best]->vdisplay)) {
+                    best = i;
+                    continue;
+                }
+                if ((modes[i]->hdisplay == modes[best]->hdisplay) &&
+                    (modes[i]->vdisplay == modes[best]->vdisplay)) {
+                    if (!rate) {
+                        /* Higher is better, right? */
+                        if (calculate_rate(modes[i]) > calculate_rate(modes[best])) {
+                            best = i;
+                        }
+                    } else {
+                        if (SDL_abs(calculate_rate(modes[i])-rate) < SDL_abs(calculate_rate(modes[best])-rate)) {
+                            best = i;
+                        }
+                    }
+                }
+            }
+            if (best >= 0) {
+#ifdef X11MODES_DEBUG
+                printf("Best Mode %d: %d x %d @ %d\n", best,
+                        modes[best]->hdisplay, modes[best]->vdisplay,
+                        calculate_rate(modes[best]));
+#endif
+                SDL_NAME(XF86VidModeSwitchToMode)(display, data->screen, modes[best]);
+            }
+            XFree(modes);
+        }
+        return;
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_VIDMODE */
 }
 
 int
 X11_SetDisplayMode(_THIS, SDL_DisplayMode * mode)
 {
-    //SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
-    return -1;
+    Display *display = ((SDL_VideoData *) _this->driverdata)->display;
+    SDL_DisplayData *data = (SDL_DisplayData *) SDL_CurrentDisplay.driverdata;
+
+    set_best_resolution(display, data, mode->w, mode->h, mode->refresh_rate);
+    return 0;
 }
 
 void