ref: 2b97c9ff54dc4551cb235a3bb98e2b72989780e4
dir: /src/i_video.c/
// // Copyright(C) 1993-1996 Id Software, Inc. // 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. // // DESCRIPTION: // DOOM graphics stuff for SDL. // #include "SDL.h" #include <stdlib.h> #include <ctype.h> #include <math.h> #include <string.h> #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> #endif #include "icon.c" #include "config.h" #include "deh_str.h" #include "doomtype.h" #include "doomkeys.h" #include "i_joystick.h" #include "i_system.h" #include "i_swap.h" #include "i_timer.h" #include "i_video.h" #include "m_argv.h" #include "m_config.h" #include "m_misc.h" #include "tables.h" #include "v_diskicon.h" #include "v_video.h" #include "w_wad.h" #include "z_zone.h" static const int scancode_translate_table[] = SCANCODE_TO_KEYS_ARRAY; // Lookup table for mapping ASCII characters to their equivalent when // shift is pressed on an American layout keyboard: static const char shiftxform[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ' ', '!', '"', '#', '$', '%', '&', '"', // shift-' '(', ')', '*', '+', '<', // shift-, '_', // shift-- '>', // shift-. '?', // shift-/ ')', // shift-0 '!', // shift-1 '@', // shift-2 '#', // shift-3 '$', // shift-4 '%', // shift-5 '^', // shift-6 '&', // shift-7 '*', // shift-8 '(', // shift-9 ':', ':', // shift-; '<', '+', // shift-= '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', // shift-[ '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK ']', // shift-] '"', '_', '\'', // shift-` 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', 127 }; // SDL video driver name char *video_driver = ""; // Window position: static char *window_position = ""; // These are (1) the window (or the full screen) that our game is rendered to // and (2) the renderer that scales the texture (see below) into this window. static SDL_Window *screen; static SDL_Renderer *renderer; // Window title static char *window_title = ""; // These are (1) the 320x200x8 paletted buffer that we draw to (i.e. the one // that holds I_VideoBuffer), (2) the 320x200x32 RGBA intermediate buffer that // we blit the former buffer to, (3) the intermediate 320x200 texture that we // load the RGBA buffer to and that we render into another texture (4) which // is upscaled by an integer factor UPSCALE using "nearest" scaling and which // in turn is finally rendered to screen using "linear" scaling. static SDL_Surface *screenbuffer = NULL; static SDL_Surface *rgbabuffer = NULL; static SDL_Texture *texture = NULL; static SDL_Texture *texture_upscaled = NULL; static SDL_Rect blit_rect = { 0, 0, SCREENWIDTH, SCREENHEIGHT }; // palette static SDL_Color palette[256]; static boolean palette_to_set; // display has been set up? static boolean initialized = false; // disable mouse? static boolean nomouse = false; int usemouse = 1; // Bit mask of mouse button state. static unsigned int mouse_button_state = 0; // Disallow mouse and joystick movement to cause forward/backward // motion. Specified with the '-novert' command line parameter. // This is an int to allow saving to config file int novert = 0; // Save screenshots in PNG format. int png_screenshots = 0; // Display disk activity indicator. int show_diskicon = 1; // Only display the disk icon if more then this much bytes have been read // during the previous tic. int diskicon_readbytes = 0; // Screen width and height, from configuration file. int screen_width = SCREENWIDTH; int screen_height = SCREENHEIGHT; // Automatically adjust video settings if the selected mode is // not a valid video mode. static int autoadjust_video_settings = 1; // Run in full screen mode? (int type for config code) int fullscreen = true; // Aspect ratio correction mode int aspect_ratio_correct = true; // Time to wait for the screen to settle on startup before starting the // game (ms) static int startup_delay = 1000; // Grab the mouse? (int type for config code) static int grabmouse = true; // The screen buffer; this is modified to draw things to the screen byte *I_VideoBuffer = NULL; // If true, game is running as a screensaver boolean screensaver_mode = false; // Flag indicating whether the screen is currently visible: // when the screen isnt visible, don't render the screen boolean screenvisible = true; // If true, we display dots at the bottom of the screen to // indicate FPS. static boolean display_fps_dots; // If this is true, the screen is rendered but not blitted to the // video buffer. static boolean noblit; // Callback function to invoke to determine whether to grab the // mouse pointer. static grabmouse_callback_t grabmouse_callback = NULL; // Does the window currently have focus? static boolean window_focused = true; // Window resize state. static boolean need_resize = false; static unsigned int resize_w, resize_h; static unsigned int last_resize_time; // If true, keyboard mapping is ignored, like in Vanilla Doom. // The sensible thing to do is to disable this if you have a non-US // keyboard. int vanilla_keyboard_mapping = true; // Is the shift key currently down? static int shiftdown = 0; // Mouse acceleration // // This emulates some of the behavior of DOS mouse drivers by increasing // the speed when the mouse is moved fast. // // The mouse input values are input directly to the game, but when // the values exceed the value of mouse_threshold, they are multiplied // by mouse_acceleration to increase the speed. float mouse_acceleration = 2.0; int mouse_threshold = 10; // Gamma correction level to use int usegamma = 0; static boolean MouseShouldBeGrabbed() { // never grab the mouse when in screensaver mode if (screensaver_mode) return false; // if the window doesn't have focus, never grab it if (!window_focused) return false; // always grab the mouse when full screen (dont want to // see the mouse pointer) if (fullscreen) return true; // Don't grab the mouse if mouse input is disabled if (!usemouse || nomouse) return false; // if we specify not to grab the mouse, never grab if (!grabmouse) return false; // Invoke the grabmouse callback function to determine whether // the mouse should be grabbed if (grabmouse_callback != NULL) { return grabmouse_callback(); } else { return true; } } void I_SetGrabMouseCallback(grabmouse_callback_t func) { grabmouse_callback = func; } // Set the variable controlling FPS dots. void I_DisplayFPSDots(boolean dots_on) { display_fps_dots = dots_on; } // Show or hide the mouse cursor. We have to use different techniques // depending on the OS. static void SetShowCursor(boolean show) { SDL_ShowCursor(show); // When the cursor is hidden, grab the input. if (!screensaver_mode) { SDL_SetWindowGrab(screen, !show); } } // // Translates the SDL key // static int TranslateKey(SDL_Keysym *sym) { int scancode = sym->scancode; switch (scancode) { case SDL_SCANCODE_LCTRL: case SDL_SCANCODE_RCTRL: return KEY_RCTRL; case SDL_SCANCODE_LSHIFT: case SDL_SCANCODE_RSHIFT: return KEY_RSHIFT; case SDL_SCANCODE_LALT: return KEY_LALT; case SDL_SCANCODE_RALT: return KEY_RALT; default: if (scancode >= 0 && scancode < arrlen(scancode_translate_table)) { return scancode_translate_table[scancode]; } else { return 0; } } } void I_ShutdownGraphics(void) { if (initialized) { SetShowCursor(true); SDL_QuitSubSystem(SDL_INIT_VIDEO); initialized = false; } } // // I_StartFrame // void I_StartFrame (void) { // er? } static void UpdateMouseButtonState(unsigned int button, boolean on) { event_t event; if (button < SDL_BUTTON_LEFT || button > MAX_MOUSE_BUTTONS) { return; } // Note: button "0" is left, button "1" is right, // button "2" is middle for Doom. This is different // to how SDL sees things. switch (button) { case SDL_BUTTON_LEFT: button = 0; break; case SDL_BUTTON_RIGHT: button = 1; break; case SDL_BUTTON_MIDDLE: button = 2; break; default: // SDL buttons are indexed from 1. --button; break; } // Turn bit representing this button on or off. if (on) { mouse_button_state |= (1 << button); } else { mouse_button_state &= ~(1 << button); } // Post an event with the new button state. event.type = ev_mouse; event.data1 = mouse_button_state; event.data2 = event.data3 = 0; D_PostEvent(&event); } static int AccelerateMouse(int val) { if (val < 0) return -AccelerateMouse(-val); if (val > mouse_threshold) { return (int)((val - mouse_threshold) * mouse_acceleration + mouse_threshold); } else { return val; } } // Get the equivalent ASCII (Unicode?) character for a keypress. static int GetTypedChar(SDL_Event *event) { // If we're strictly emulating Vanilla, we should always act like // we're using a US layout keyboard (in ev_keydown, data1=data2). // Otherwise we should use the native key mapping. if (vanilla_keyboard_mapping) { int result = TranslateKey(&event->key.keysym); // If shift is held down, apply the original uppercase // translation table used under DOS. if ((SDL_GetModState() & KMOD_SHIFT) != 0 && result >= 0 && result < arrlen(shiftxform)) { result = shiftxform[result]; } return result; } else { int unicode = event->key.keysym.sym; if (unicode < 128) { return unicode; } else { return 0; } } } static void UpdateShiftStatus(SDL_Event *event) { int change; if (event->type == SDL_KEYDOWN) { change = 1; } else if (event->type == SDL_KEYUP) { change = -1; } else { return; } if (event->key.keysym.sym == SDLK_LSHIFT || event->key.keysym.sym == SDLK_RSHIFT) { shiftdown += change; } } static void HandleWindowEvent(SDL_WindowEvent *event) { switch (event->event) { #if 0 // SDL2-TODO case SDL_ACTIVEEVENT: // need to update our focus state UpdateFocus(); break; #endif case SDL_WINDOWEVENT_EXPOSED: palette_to_set = true; break; case SDL_WINDOWEVENT_RESIZED: need_resize = true; resize_w = event->data1; resize_h = event->data2; last_resize_time = SDL_GetTicks(); break; // Don't render the screen when the window is minimized: case SDL_WINDOWEVENT_MINIMIZED: screenvisible = false; break; case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_RESTORED: screenvisible = true; break; // Update the value of window_focused when we get a focus event // // We try to make ourselves be well-behaved: the grab on the mouse // is removed if we lose focus (such as a popup window appearing), // and we dont move the mouse around if we aren't focused either. case SDL_WINDOWEVENT_ENTER: case SDL_WINDOWEVENT_FOCUS_GAINED: window_focused = true; break; case SDL_WINDOWEVENT_LEAVE: case SDL_WINDOWEVENT_FOCUS_LOST: window_focused = false; break; default: break; } } void I_GetEvent(void) { SDL_Event sdlevent; event_t event; // possibly not needed SDL_PumpEvents(); // put event-grabbing stuff in here while (SDL_PollEvent(&sdlevent)) { // ignore mouse events when the window is not focused if (!window_focused && (sdlevent.type == SDL_MOUSEMOTION || sdlevent.type == SDL_MOUSEBUTTONDOWN || sdlevent.type == SDL_MOUSEBUTTONUP)) { continue; } if (screensaver_mode && sdlevent.type == SDL_QUIT) { I_Quit(); } UpdateShiftStatus(&sdlevent); // process event switch (sdlevent.type) { case SDL_KEYDOWN: // data1 has the key pressed, data2 has the character // (shift-translated, etc) event.type = ev_keydown; event.data1 = TranslateKey(&sdlevent.key.keysym); event.data2 = GetTypedChar(&sdlevent); if (event.data1 != 0) { D_PostEvent(&event); } break; case SDL_KEYUP: event.type = ev_keyup; event.data1 = TranslateKey(&sdlevent.key.keysym); // data2 is just initialized to zero for ev_keyup. // For ev_keydown it's the shifted Unicode character // that was typed, but if something wants to detect // key releases it should do so based on data1 // (key ID), not the printable char. event.data2 = 0; if (event.data1 != 0) { D_PostEvent(&event); } break; /* case SDL_MOUSEMOTION: event.type = ev_mouse; event.data1 = mouse_button_state; event.data2 = AccelerateMouse(sdlevent.motion.xrel); event.data3 = -AccelerateMouse(sdlevent.motion.yrel); D_PostEvent(&event); break; */ case SDL_MOUSEBUTTONDOWN: if (usemouse && !nomouse) { UpdateMouseButtonState(sdlevent.button.button, true); } break; case SDL_MOUSEBUTTONUP: if (usemouse && !nomouse) { UpdateMouseButtonState(sdlevent.button.button, false); } break; case SDL_QUIT: event.type = ev_quit; D_PostEvent(&event); break; case SDL_WINDOWEVENT: if (sdlevent.window.windowID == SDL_GetWindowID(screen)) { HandleWindowEvent(&sdlevent.window); } break; default: break; } } } // // Read the change in mouse state to generate mouse motion events // // This is to combine all mouse movement for a tic into one mouse // motion event. static void I_ReadMouse(void) { int x, y; event_t ev; SDL_GetRelativeMouseState(&x, &y); if (x != 0 || y != 0) { ev.type = ev_mouse; ev.data1 = mouse_button_state; ev.data2 = AccelerateMouse(x); if (!novert) { ev.data3 = -AccelerateMouse(y); } else { ev.data3 = 0; } D_PostEvent(&ev); } } // // I_StartTic // void I_StartTic (void) { if (!initialized) { return; } I_GetEvent(); if (usemouse && !nomouse) { I_ReadMouse(); } I_UpdateJoystick(); } // // I_UpdateNoBlit // void I_UpdateNoBlit (void) { // what is this? } static void UpdateGrab(void) { static boolean currently_grabbed = false; boolean grab; grab = MouseShouldBeGrabbed(); if (screensaver_mode) { // Hide the cursor in screensaver mode SetShowCursor(false); } else if (grab && !currently_grabbed) { SetShowCursor(false); } else if (!grab && currently_grabbed) { int screen_w, screen_h; SetShowCursor(true); // When releasing the mouse from grab, warp the mouse cursor to // the bottom-right of the screen. This is a minimally distracting // place for it to appear - we may only have released the grab // because we're at an end of level intermission screen, for // example. SDL_GetWindowSize(screen, &screen_w, &screen_h); SDL_WarpMouseInWindow(screen, screen_w - 16, screen_h - 16); SDL_GetRelativeMouseState(NULL, NULL); } currently_grabbed = grab; } static void CreateUpscaledTexture(int w, int h) { const int actualheight = aspect_ratio_correct ? SCREENHEIGHT_4_3 : SCREENHEIGHT; int h_upscale, w_upscale; static int h_upscale_old, w_upscale_old; // When the screen or window dimensions do not match the aspect ratio // of the texture, the rendered area is scaled down to fit. Calculate // the actual dimensions of the rendered area. if (w * actualheight < h * SCREENWIDTH) { // Tall window. h = w * actualheight / SCREENWIDTH; } else { // Wide window. w = h * SCREENWIDTH / actualheight; } // If one screen dimension matches an integer multiple of the original // resolution, there is no need to overscale in this direction. h_upscale = h / SCREENHEIGHT; if (!h || h % SCREENHEIGHT) { h_upscale++; } w_upscale = w / SCREENWIDTH; if (!w || w % SCREENWIDTH) { w_upscale++; } // Limit maximum texture dimensions to 1600x1200. if (h_upscale > 6) { h_upscale = 6; } if (w_upscale > 5) { w_upscale = 5; } // Create a new texture only if the upscale factors have actually changed. if (h_upscale == h_upscale_old && w_upscale == w_upscale_old) { return; } h_upscale_old = h_upscale; w_upscale_old = w_upscale; if (texture_upscaled) { SDL_DestroyTexture(texture_upscaled); } // Set the scaling quality for rendering the upscaled texture to "linear", // which looks much softer and smoother than "nearest" but does a better // job at downscaling from the upscaled texture to screen. SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); texture_upscaled = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, w_upscale*SCREENWIDTH, h_upscale*SCREENHEIGHT); } // // I_FinishUpdate // void I_FinishUpdate (void) { static int lasttic; int tics; int i; if (!initialized) return; if (noblit) return; if (need_resize && SDL_GetTicks() > last_resize_time + 1000/TICRATE) { CreateUpscaledTexture(resize_w, resize_h); screen_width = resize_w; screen_height = resize_h; need_resize = false; palette_to_set = true; } UpdateGrab(); #if 0 // SDL2-TODO // Don't update the screen if the window isn't visible. // Not doing this breaks under Windows when we alt-tab away // while fullscreen. if (!(SDL_GetAppState() & SDL_APPACTIVE)) return; #endif // draws little dots on the bottom of the screen if (display_fps_dots) { i = I_GetTime(); tics = i - lasttic; lasttic = i; if (tics > 20) tics = 20; for (i=0 ; i<tics*4 ; i+=4) I_VideoBuffer[ (SCREENHEIGHT-1)*SCREENWIDTH + i] = 0xff; for ( ; i<20*4 ; i+=4) I_VideoBuffer[ (SCREENHEIGHT-1)*SCREENWIDTH + i] = 0x0; } if (palette_to_set) { SDL_SetPaletteColors(screenbuffer->format->palette, palette, 0, 256); palette_to_set = false; } // Blit from the paletted 8-bit screen buffer to the intermediate // 32-bit RGBA buffer that we can load into the texture. SDL_LowerBlit(screenbuffer, &blit_rect, rgbabuffer, &blit_rect); // Update the intermediate texture with the contents of the RGBA buffer. SDL_UpdateTexture(texture, NULL, rgbabuffer->pixels, rgbabuffer->pitch); // Make sure the pillarboxes are kept clear each frame. SDL_RenderClear(renderer); // Render this intermediate texture into the upscaled texture // using "nearest" integer scaling. SDL_SetRenderTarget(renderer, texture_upscaled); SDL_RenderCopy(renderer, texture, NULL, NULL); // Finally, render this upscaled texture to screen using linear scaling. SDL_SetRenderTarget(renderer, NULL); SDL_RenderCopy(renderer, texture_upscaled, NULL, NULL); // Draw! SDL_RenderPresent(renderer); } // // I_ReadScreen // void I_ReadScreen (byte* scr) { memcpy(scr, I_VideoBuffer, SCREENWIDTH*SCREENHEIGHT*sizeof(*scr)); } // // I_SetPalette // void I_SetPalette (byte *doompalette) { int i; for (i=0; i<256; ++i) { // Zero out the bottom two bits of each channel - the PC VGA // controller only supports 6 bits of accuracy. palette[i].r = gammatable[usegamma][*doompalette++] & ~3; palette[i].g = gammatable[usegamma][*doompalette++] & ~3; palette[i].b = gammatable[usegamma][*doompalette++] & ~3; } palette_to_set = true; } // Given an RGB value, find the closest matching palette index. int I_GetPaletteIndex(int r, int g, int b) { int best, best_diff, diff; int i; best = 0; best_diff = INT_MAX; for (i = 0; i < 256; ++i) { diff = (r - palette[i].r) * (r - palette[i].r) + (g - palette[i].g) * (g - palette[i].g) + (b - palette[i].b) * (b - palette[i].b); if (diff < best_diff) { best = i; best_diff = diff; } if (diff == 0) { break; } } return best; } // // Set the window title // void I_SetWindowTitle(char *title) { window_title = title; } // // Call the SDL function to set the window title, based on // the title set with I_SetWindowTitle. // void I_InitWindowTitle(void) { char *buf; buf = M_StringJoin(window_title, " - ", PACKAGE_STRING, NULL); SDL_SetWindowTitle(screen, buf); free(buf); } // Set the application icon void I_InitWindowIcon(void) { SDL_Surface *surface; surface = SDL_CreateRGBSurfaceFrom((void *) icon_data, icon_w, icon_h, 32, icon_w * 4, 0xff << 24, 0xff << 16, 0xff << 8, 0xff << 0); SDL_SetWindowIcon(screen, surface); SDL_FreeSurface(surface); } // Set video size to a particular scale factor (1x, 2x, 3x, etc.) static void SetScaleFactor(int factor) { int w, h; // Pick 320x200 or 320x240, depending on aspect ratio correct if (aspect_ratio_correct) { w = SCREENWIDTH; h = SCREENHEIGHT_4_3; } else { w = SCREENWIDTH; h = SCREENHEIGHT; } screen_width = w * factor; screen_height = h * factor; } void I_GraphicsCheckCommandLine(void) { int i; //! // @vanilla // // Disable blitting the screen. // noblit = M_CheckParm ("-noblit"); //! // @category video // // Grab the mouse when running in windowed mode. // if (M_CheckParm("-grabmouse")) { grabmouse = true; } //! // @category video // // Don't grab the mouse when running in windowed mode. // if (M_CheckParm("-nograbmouse")) { grabmouse = false; } // default to fullscreen mode, allow override with command line // nofullscreen because we love prboom //! // @category video // // Run in a window. // if (M_CheckParm("-window") || M_CheckParm("-nofullscreen")) { fullscreen = false; } //! // @category video // // Run in fullscreen mode. // if (M_CheckParm("-fullscreen")) { fullscreen = true; } //! // @category video // // Disable the mouse. // nomouse = M_CheckParm("-nomouse") > 0; //! // @category video // @arg <x> // // Specify the screen width, in pixels. // i = M_CheckParmWithArgs("-width", 1); if (i > 0) { screen_width = atoi(myargv[i + 1]); } //! // @category video // @arg <y> // // Specify the screen height, in pixels. // i = M_CheckParmWithArgs("-height", 1); if (i > 0) { screen_height = atoi(myargv[i + 1]); } //! // @category video // @arg <WxY>[wf] // // Specify the dimensions of the window or fullscreen mode. An // optional letter of w or f appended to the dimensions selects // windowed or fullscreen mode. i = M_CheckParmWithArgs("-geometry", 1); if (i > 0) { int w, h, s; char f; s = sscanf(myargv[i + 1], "%ix%i%1c", &w, &h, &f); if (s == 2 || s == 3) { screen_width = w; screen_height = h; if (s == 3 && f == 'f') { fullscreen = true; } else if (s == 3 && f == 'w') { fullscreen = false; } } } //! // @category video // // Don't scale up the screen. // if (M_CheckParm("-1")) { SetScaleFactor(1); } //! // @category video // // Double up the screen to 2x its normal size. // if (M_CheckParm("-2")) { SetScaleFactor(2); } //! // @category video // // Double up the screen to 3x its normal size. // if (M_CheckParm("-3")) { SetScaleFactor(3); } //! // @category video // // Disable vertical mouse movement. // if (M_CheckParm("-novert")) { novert = true; } //! // @category video // // Enable vertical mouse movement. // if (M_CheckParm("-nonovert")) { novert = false; } } // Check if we have been invoked as a screensaver by xscreensaver. void I_CheckIsScreensaver(void) { char *env; env = getenv("XSCREENSAVER_WINDOW"); if (env != NULL) { screensaver_mode = true; } } static void SetSDLVideoDriver(void) { // Allow a default value for the SDL video driver to be specified // in the configuration file. if (strcmp(video_driver, "") != 0) { char *env_string; env_string = M_StringJoin("SDL_VIDEODRIVER=", video_driver, NULL); putenv(env_string); free(env_string); } } static void SetWindowPositionVars(void) { char buf[64]; int x, y; if (window_position == NULL || !strcmp(window_position, "")) { return; } if (!strcmp(window_position, "center")) { putenv("SDL_VIDEO_CENTERED=1"); } else if (sscanf(window_position, "%i,%i", &x, &y) == 2) { M_snprintf(buf, sizeof(buf), "SDL_VIDEO_WINDOW_POS=%i,%i", x, y); putenv(buf); } } static void SetVideoMode(int w, int h) { byte *doompal; int flags = 0; doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE); // If we are already running, we need to free the screenbuffer // surface before setting the new mode. if (screenbuffer != NULL) { SDL_FreeSurface(screenbuffer); screenbuffer = NULL; } // Close the current window. if (screen != NULL) { SDL_DestroyWindow(screen); screen = NULL; } // Set the video mode. flags = 0; if (fullscreen) { // This flags means "Never change the screen resolution! Instead, // draw to the entire screen by scaling the texture appropriately". flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else { // In windowed mode, the window can be resized while the game is // running. This feature is disabled on OS X, as it adds an ugly // scroll handle to the corner of the screen. flags |= SDL_WINDOW_RESIZABLE; } // Create window and renderer contexts. We set the window title // later anyway and leave the window position "undefined". If "flags" // contains the fullscreen flag (see above), then w and h are ignored. screen = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, flags); if (screen == NULL) { I_Error("Error creating window for video mode %ix%i: %s\n", w, h, SDL_GetError()); } // If we are running fullscreen, the whole screen is our "window". if (fullscreen) { SDL_DisplayMode mode; // We do not change the video mode to run fullscreen but scale to fill // the desktop that "screen" is assigned to. So, use desktop dimensions // to calculate the size of the upscaled texture. SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(screen), &mode); h = mode.h; w = mode.w; } // The SDL_RENDERER_TARGETTEXTURE flag is required to render the // intermediate texture into the upscaled texture. renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_TARGETTEXTURE); if (renderer == NULL) { I_Error("Error creating renderer for video mode %ix%i: %s\n", w, h, SDL_GetError()); } // Important: Set the "logical size" of the rendering context. At the same // time this also defines the aspect ratio that is preserved while scaling // and stretching the texture into the window. SDL_RenderSetLogicalSize(renderer, SCREENWIDTH, aspect_ratio_correct ? SCREENHEIGHT_4_3 : SCREENHEIGHT); I_InitWindowTitle(); I_InitWindowIcon(); // Blank out the full screen area in case there is any junk in // the borders that won't otherwise be overwritten. SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); // Create the 8-bit paletted and the 32-bit RGBA screenbuffer surfaces. screenbuffer = SDL_CreateRGBSurface(0, SCREENWIDTH, SCREENHEIGHT, 8, 0, 0, 0, 0); SDL_FillRect(screenbuffer, NULL, 0); rgbabuffer = SDL_CreateRGBSurface(0, SCREENWIDTH, SCREENHEIGHT, 32, 0, 0, 0, 0); SDL_FillRect(rgbabuffer, NULL, 0); // Set the scaling quality for rendering the intermediate texture into // the upscaled texture to "nearest", which is gritty and pixelated and // resembles software scaling pretty well. SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Create the intermediate texture that the RGBA surface gets loaded into. // The SDL_TEXTUREACCESS_STREAMING flag means that this texture's content // is going to change frequently. texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREENWIDTH, SCREENHEIGHT); // Initially create the upscaled texture for rendering to screen CreateUpscaledTexture(w, h); } void I_InitGraphics(void) { SDL_Event dummy; byte *doompal; char *env; // Pass through the XSCREENSAVER_WINDOW environment variable to // SDL_WINDOWID, to embed the SDL window into the Xscreensaver // window. env = getenv("XSCREENSAVER_WINDOW"); if (env != NULL) { char winenv[30]; int winid; sscanf(env, "0x%x", &winid); M_snprintf(winenv, sizeof(winenv), "SDL_WINDOWID=%i", winid); putenv(winenv); } SetSDLVideoDriver(); SetWindowPositionVars(); if (SDL_Init(SDL_INIT_VIDEO) < 0) { I_Error("Failed to initialize video: %s", SDL_GetError()); } // Warning to OS X users... though they might never see it :( #ifdef __MACOSX__ if (fullscreen) { printf("Some old versions of OS X might crash in fullscreen mode.\n" "If this happens to you, switch back to windowed mode.\n"); } #endif // // Enter into graphics mode. // // When in screensaver mode, run full screen and auto detect // screen dimensions (don't change video mode) // if (screensaver_mode) { SetVideoMode(0, 0); } else { // SDL2-TODO: sanity check screen_width/screen_height against // screen modes listed as available by LibSDL, and auto-adjust // when the selected mode is not available. SetVideoMode(screen_width, screen_height); } // Start with a clear black screen // (screen will be flipped after we set the palette) SDL_FillRect(screenbuffer, NULL, 0); // Set the palette doompal = W_CacheLumpName(DEH_String("PLAYPAL"), PU_CACHE); I_SetPalette(doompal); SDL_SetPaletteColors(screenbuffer->format->palette, palette, 0, 256); // SDL2-TODO UpdateFocus(); UpdateGrab(); // On some systems, it takes a second or so for the screen to settle // after changing modes. We include the option to add a delay when // setting the screen mode, so that the game doesn't start immediately // with the player unable to see anything. if (fullscreen && !screensaver_mode) { SDL_Delay(startup_delay); } // The actual 320x200 canvas that we draw to. This is the pixel buffer of // the 8-bit paletted screen buffer that gets blit on an intermediate // 32-bit RGBA screen buffer that gets loaded into a texture that gets // finally rendered into our window or full screen in I_FinishUpdate(). I_VideoBuffer = screenbuffer->pixels; V_RestoreBuffer(); // Clear the screen to black. memset(I_VideoBuffer, 0, SCREENWIDTH * SCREENHEIGHT); // We need SDL to give us translated versions of keys as well // SDL2-TODO SDL_EnableUNICODE(1); // Repeat key presses - this is what Vanilla Doom does // Not sure about repeat rate - probably dependent on which DOS // driver is used. This is good enough though. // SDL2-TODO SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // clear out any events waiting at the start and center the mouse while (SDL_PollEvent(&dummy)); initialized = true; // Call I_ShutdownGraphics on quit I_AtExit(I_ShutdownGraphics, true); } // Bind all variables controlling video options into the configuration // file system. void I_BindVideoVariables(void) { M_BindIntVariable("use_mouse", &usemouse); M_BindIntVariable("autoadjust_video_settings", &autoadjust_video_settings); M_BindIntVariable("fullscreen", &fullscreen); M_BindIntVariable("aspect_ratio_correct", &aspect_ratio_correct); M_BindIntVariable("startup_delay", &startup_delay); M_BindIntVariable("screen_width", &screen_width); M_BindIntVariable("screen_height", &screen_height); M_BindIntVariable("grabmouse", &grabmouse); M_BindFloatVariable("mouse_acceleration", &mouse_acceleration); M_BindIntVariable("mouse_threshold", &mouse_threshold); M_BindStringVariable("video_driver", &video_driver); M_BindStringVariable("window_position", &window_position); M_BindIntVariable("usegamma", &usegamma); M_BindIntVariable("vanilla_keyboard_mapping", &vanilla_keyboard_mapping); M_BindIntVariable("novert", &novert); M_BindIntVariable("png_screenshots", &png_screenshots); // 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 }