ref: 4946e59ea3e7557d261655075035165290d9080e
dir: /src/setup/display.c/
// // Copyright(C) 2005-2014 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program 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 General Public License for more details. // #include <stdlib.h> #include <string.h> #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> #endif #include "textscreen.h" #include "m_config.h" #include "m_misc.h" #include "mode.h" #include "display.h" #include "config.h" #define WINDOW_HELP_URL "https://www.chocolate-doom.org/setup-display" extern void RestartTextscreen(void); typedef struct { char *description; int bpp; } pixel_depth_t; // List of supported pixel depths. static pixel_depth_t pixel_depths[] = { { "8-bit", 8 }, { "16-bit", 16 }, { "24-bit", 24 }, { "32-bit", 32 }, }; // List of strings containing supported pixel depths. static char **supported_bpps; static int num_supported_bpps; typedef struct { int w, h; } screen_mode_t; // List of aspect ratio-uncorrected modes static screen_mode_t screen_modes_unscaled[] = { { 320, 200 }, { 640, 400 }, { 960, 600 }, { 1280, 800 }, { 1600, 1000 }, { 0, 0}, }; // List of aspect ratio-corrected modes static screen_mode_t screen_modes_scaled[] = { { 256, 200 }, { 320, 240 }, { 512, 400 }, { 640, 480 }, { 800, 600 }, { 960, 720 }, { 1024, 800 }, { 1280, 960 }, { 1600, 1200 }, { 0, 0}, }; // List of fullscreen modes generated at runtime static screen_mode_t *screen_modes_fullscreen = NULL; static int num_screen_modes_fullscreen; static int vidmode = 0; static char *video_driver = ""; static char *window_position = ""; static int autoadjust_video_settings = 1; static int aspect_ratio_correct = 1; static int fullscreen = 1; static int screen_width = 320; static int screen_height = 200; static int screen_bpp = 0; static int startup_delay = 1000; static int usegamma = 0; int graphical_startup = 1; int show_endoom = 1; int show_diskicon = 1; int png_screenshots = 0; // These are the last screen width/height values that were chosen by the // user. These are used when finding the "nearest" mode, so when // changing the fullscreen / aspect ratio options, the setting does not // jump around. static int selected_screen_width = 0, selected_screen_height; // Index into the supported_bpps of the selected pixel depth. static int selected_bpp = 0; static int system_video_env_set; // Set the SDL_VIDEODRIVER environment variable void SetDisplayDriver(void) { static int first_time = 1; if (first_time) { system_video_env_set = getenv("SDL_VIDEODRIVER") != NULL; first_time = 0; } // Don't override the command line environment, if it has been set. if (system_video_env_set) { return; } // Use the value from the configuration file, if it has been set. if (strcmp(video_driver, "") != 0) { char *env_string; env_string = M_StringJoin("SDL_VIDEODRIVER=", video_driver, NULL); putenv(env_string); free(env_string); } } // Query SDL as to whether any fullscreen modes are available for the // specified pixel depth. static int PixelDepthSupported(int bpp) { SDL_PixelFormat format; SDL_Rect **modes; format.BitsPerPixel = bpp; format.BytesPerPixel = (bpp + 7) / 8; modes = SDL_ListModes(&format, SDL_FULLSCREEN); return modes != NULL; } // Query SDL and populate the supported_bpps array. static void IdentifyPixelDepths(void) { unsigned int i; unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths); if (supported_bpps != NULL) { free(supported_bpps); } supported_bpps = malloc(sizeof(char *) * num_depths); num_supported_bpps = 0; // Check each bit depth to determine if modes are available. for (i = 0; i < num_depths; ++i) { // If modes are available, add this bit depth to the list. if (PixelDepthSupported(pixel_depths[i].bpp)) { supported_bpps[num_supported_bpps] = pixel_depths[i].description; ++num_supported_bpps; } } // No supported pixel depths? That's kind of a problem. Add 8bpp // as a fallback. if (num_supported_bpps == 0) { supported_bpps[0] = pixel_depths[0].description; ++num_supported_bpps; } } // Get the screen pixel depth corresponding to what selected_bpp is set to. static int GetSelectedBPP(void) { unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths); unsigned int i; // Find which pixel depth is selected, and set screen_bpp. for (i = 0; i < num_depths; ++i) { if (pixel_depths[i].description == supported_bpps[selected_bpp]) { return pixel_depths[i].bpp; } } // Default fallback value. return 8; } // Get the index into supported_bpps of the specified pixel depth string. static int GetSupportedBPPIndex(char *description) { unsigned int i; for (i = 0; i < num_supported_bpps; ++i) { if (supported_bpps[i] == description) { return i; } } return -1; } // Set selected_bpp to match screen_bpp. static int TrySetSelectedBPP(void) { unsigned int num_depths = sizeof(pixel_depths) / sizeof(*pixel_depths); unsigned int i; // Search pixel_depths, find the bpp that corresponds to screen_bpp, // then set selected_bpp to match. for (i = 0; i < num_depths; ++i) { if (pixel_depths[i].bpp == screen_bpp) { selected_bpp = GetSupportedBPPIndex(pixel_depths[i].description); if (selected_bpp >= 0) { return 1; } } } return 0; } static void SetSelectedBPP(void) { const SDL_VideoInfo *info; if (TrySetSelectedBPP()) { return; } // screen_bpp does not match any supported pixel depth. Query SDL // to find out what it recommends using. info = SDL_GetVideoInfo(); if (info != NULL && info->vfmt != NULL) { screen_bpp = info->vfmt->BitsPerPixel; } // Try again. if (!TrySetSelectedBPP()) { // Give up and just use the first in the list. selected_bpp = 0; screen_bpp = GetSelectedBPP(); } } static void ModeSelected(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(mode)) { TXT_CAST_ARG(screen_mode_t, mode); screen_width = mode->w; screen_height = mode->h; // This is now the most recently selected screen width selected_screen_width = screen_width; selected_screen_height = screen_height; } static int GoodFullscreenMode(screen_mode_t *mode) { int w, h; w = mode->w; h = mode->h; // 320x200 and 640x400 are always good (special case) if ((w == 320 && h == 200) || (w == 640 && h == 400)) { return 1; } // Special case: 320x240 letterboxed mode is okay (but not aspect // ratio corrected 320x240) if (w == 320 && h == 240 && !aspect_ratio_correct) { return 1; } // Ignore all modes less than 640x480 return w >= 640 && h >= 480; } // Build screen_modes_fullscreen static void BuildFullscreenModesList(void) { SDL_PixelFormat format; SDL_Rect **modes; screen_mode_t *m1; screen_mode_t *m2; screen_mode_t m; int num_modes; int i; // Free the existing modes list, if one exists if (screen_modes_fullscreen != NULL) { free(screen_modes_fullscreen); } // Get a list of fullscreen modes and find out how many // modes are in the list. format.BitsPerPixel = screen_bpp; format.BytesPerPixel = (screen_bpp + 7) / 8; modes = SDL_ListModes(&format, SDL_FULLSCREEN); if (modes == NULL || modes == (SDL_Rect **) -1) { num_modes = 0; } else { for (num_modes=0; modes[num_modes] != NULL; ++num_modes); } // Build the screen_modes_fullscreen array screen_modes_fullscreen = malloc(sizeof(screen_mode_t) * (num_modes + 1)); for (i=0; i<num_modes; ++i) { screen_modes_fullscreen[i].w = modes[i]->w; screen_modes_fullscreen[i].h = modes[i]->h; } screen_modes_fullscreen[i].w = 0; screen_modes_fullscreen[i].h = 0; // Reverse the order of the modes list (smallest modes first) for (i=0; i<num_modes / 2; ++i) { m1 = &screen_modes_fullscreen[i]; m2 = &screen_modes_fullscreen[num_modes - 1 - i]; memcpy(&m, m1, sizeof(screen_mode_t)); memcpy(m1, m2, sizeof(screen_mode_t)); memcpy(m2, &m, sizeof(screen_mode_t)); } num_screen_modes_fullscreen = num_modes; } static int FindBestMode(screen_mode_t *modes) { int i; int best_mode; int best_mode_diff; int diff; best_mode = -1; best_mode_diff = 0; for (i=0; modes[i].w != 0; ++i) { if (fullscreen && !GoodFullscreenMode(&modes[i])) { continue; } diff = (selected_screen_width - modes[i].w) * (selected_screen_width - modes[i].w) + (selected_screen_height - modes[i].h) * (selected_screen_height - modes[i].h); if (best_mode == -1 || diff < best_mode_diff) { best_mode_diff = diff; best_mode = i; } } return best_mode; } static void GenerateModesTable(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(modes_table)) { TXT_CAST_ARG(txt_table_t, modes_table); char buf[15]; screen_mode_t *modes; txt_radiobutton_t *rbutton; int i; // Pick which modes list to use if (fullscreen) { if (screen_modes_fullscreen == NULL) { BuildFullscreenModesList(); } modes = screen_modes_fullscreen; } else if (aspect_ratio_correct) { modes = screen_modes_scaled; } else { modes = screen_modes_unscaled; } // Build the table TXT_ClearTable(modes_table); TXT_SetColumnWidths(modes_table, 14, 14, 14, 14, 14); for (i=0; modes[i].w != 0; ++i) { // Skip bad fullscreen modes if (fullscreen && !GoodFullscreenMode(&modes[i])) { continue; } M_snprintf(buf, sizeof(buf), "%ix%i", modes[i].w, modes[i].h); rbutton = TXT_NewRadioButton(buf, &vidmode, i); TXT_AddWidget(modes_table, rbutton); TXT_SignalConnect(rbutton, "selected", ModeSelected, &modes[i]); } // Find the nearest mode in the list that matches the current // settings vidmode = FindBestMode(modes); if (vidmode > 0) { screen_width = modes[vidmode].w; screen_height = modes[vidmode].h; } } // Callback invoked when the BPP selector is changed. static void UpdateBPP(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(modes_table)) { TXT_CAST_ARG(txt_table_t, modes_table); screen_bpp = GetSelectedBPP(); // Rebuild list of fullscreen modes. BuildFullscreenModesList(); GenerateModesTable(NULL, modes_table); } static void UpdateModeSeparator(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(separator)) { TXT_CAST_ARG(txt_separator_t, separator); if (fullscreen) { TXT_SetSeparatorLabel(separator, "Screen mode"); } else { TXT_SetSeparatorLabel(separator, "Window size"); } } static void AdvancedDisplayConfig(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(modes_table)) { TXT_CAST_ARG(txt_table_t, modes_table); txt_window_t *window; txt_checkbox_t *ar_checkbox; window = TXT_NewWindow("Advanced display options"); TXT_SetWindowHelpURL(window, WINDOW_HELP_URL); TXT_SetColumnWidths(window, 35); TXT_AddWidgets(window, ar_checkbox = TXT_NewCheckBox("Fix aspect ratio", &aspect_ratio_correct), NULL); if (gamemission == heretic || gamemission == hexen || gamemission == strife) { TXT_AddWidget(window, TXT_NewCheckBox("Graphical startup", &graphical_startup)); } if (gamemission == doom || gamemission == heretic || gamemission == strife) { TXT_AddWidget(window, TXT_NewCheckBox("Show ENDOOM screen on exit", &show_endoom)); } #ifdef HAVE_LIBPNG TXT_AddWidget(window, TXT_NewCheckBox("Save screenshots in PNG format", &png_screenshots)); #endif TXT_SignalConnect(ar_checkbox, "changed", GenerateModesTable, modes_table); } void ConfigDisplay(void) { txt_window_t *window; txt_table_t *modes_table; txt_separator_t *modes_separator; txt_table_t *bpp_table; txt_window_action_t *advanced_button; txt_checkbox_t *fs_checkbox; int i; int num_columns; int num_rows; int window_y; // What color depths are supported? Generate supported_bpps array // and set selected_bpp to match the current value of screen_bpp. IdentifyPixelDepths(); SetSelectedBPP(); // First time in? Initialise selected_screen_{width,height} if (selected_screen_width == 0) { selected_screen_width = screen_width; selected_screen_height = screen_height; } // Open the window window = TXT_NewWindow("Display Configuration"); TXT_SetWindowHelpURL(window, WINDOW_HELP_URL); // Some machines can have lots of video modes. This tries to // keep a limit of six lines by increasing the number of // columns. In extreme cases, the window is moved up slightly. BuildFullscreenModesList(); if (num_screen_modes_fullscreen <= 24) { num_columns = 3; } else if (num_screen_modes_fullscreen <= 40) { num_columns = 4; } else { num_columns = 5; } modes_table = TXT_NewTable(num_columns); // Build window: TXT_AddWidget(window, fs_checkbox = TXT_NewCheckBox("Full screen", &fullscreen)); if (num_supported_bpps > 1) { TXT_AddWidgets(window, TXT_NewSeparator("Color depth"), bpp_table = TXT_NewTable(4), NULL); for (i = 0; i < num_supported_bpps; ++i) { txt_radiobutton_t *button; button = TXT_NewRadioButton(supported_bpps[i], &selected_bpp, i); TXT_AddWidget(bpp_table, button); TXT_SignalConnect(button, "selected", UpdateBPP, modes_table); } } TXT_AddWidgets(window, modes_separator = TXT_NewSeparator(""), modes_table, NULL); TXT_SignalConnect(fs_checkbox, "changed", GenerateModesTable, modes_table); TXT_SignalConnect(fs_checkbox, "changed", UpdateModeSeparator, modes_separator); // How many rows high will the configuration window be? // Need to take into account number of fullscreen modes, and also // number of supported pixel depths. // The windowed modes list is four rows, so take the maximum of // windowed and fullscreen. num_rows = (num_screen_modes_fullscreen + num_columns - 1) / num_columns; if (num_rows < 4) { num_rows = 4; } if (num_supported_bpps > 1) { num_rows += 2; } if (num_rows < 14) { window_y = 8 - ((num_rows + 1) / 2); } else { window_y = 1; } // The window is set at a fixed vertical position. This keeps // the top of the window stationary when switching between // fullscreen and windowed mode (which causes the window's // height to change). TXT_SetWindowPosition(window, TXT_HORIZ_CENTER, TXT_VERT_TOP, TXT_SCREEN_W / 2, window_y); GenerateModesTable(NULL, modes_table); UpdateModeSeparator(NULL, modes_separator); // Button to open "advanced" window. // Need to pass a pointer to the modes table, as some of the options // in there trigger a rebuild of it. advanced_button = TXT_NewWindowAction('a', "Advanced"); TXT_SetWindowAction(window, TXT_HORIZ_CENTER, advanced_button); TXT_SignalConnect(advanced_button, "pressed", AdvancedDisplayConfig, modes_table); } void BindDisplayVariables(void) { M_BindIntVariable("autoadjust_video_settings", &autoadjust_video_settings); M_BindIntVariable("aspect_ratio_correct", &aspect_ratio_correct); M_BindIntVariable("fullscreen", &fullscreen); M_BindIntVariable("screen_width", &screen_width); M_BindIntVariable("screen_height", &screen_height); M_BindIntVariable("screen_bpp", &screen_bpp); M_BindIntVariable("startup_delay", &startup_delay); M_BindStringVariable("video_driver", &video_driver); M_BindStringVariable("window_position", &window_position); M_BindIntVariable("usegamma", &usegamma); M_BindIntVariable("png_screenshots", &png_screenshots); if (gamemission == doom || gamemission == heretic || gamemission == strife) { M_BindIntVariable("show_endoom", &show_endoom); } if (gamemission == doom || gamemission == strife) { M_BindIntVariable("show_diskicon", &show_diskicon); } if (gamemission == heretic || gamemission == hexen || gamemission == strife) { M_BindIntVariable("graphical_startup", &graphical_startup); } // Windows Vista or later? Set screen color depth to // 32 bits per pixel, as 8-bit palettized screen modes // don't work properly in recent versions. #if defined(_WIN32) && !defined(_WIN32_WCE) { OSVERSIONINFOEX version_info; ZeroMemory(&version_info, sizeof(OSVERSIONINFOEX)); version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); GetVersionEx((OSVERSIONINFO *) &version_info); if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT && version_info.dwMajorVersion >= 6) { screen_bpp = 32; } } #endif // Disable fullscreen by default on OS X, as there is an SDL bug // where some old versions of OS X (<= Snow Leopard) crash. #ifdef __MACOSX__ fullscreen = 0; screen_width = 800; screen_height = 600; #endif }