// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // Copyright (C) 1998-2000 by DooM Legacy Team. // // 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. //----------------------------------------------------------------------------- /// \file /// \brief win32 video driver for Doom Legacy #include "../doomdef.h" #include #include #include "../d_clisrv.h" #include "../i_system.h" #include "../m_argv.h" #include "../v_video.h" #include "../st_stuff.h" #include "../i_video.h" #include "../z_zone.h" #include "fabdxlib.h" #include "win_main.h" #include "../command.h" #include "../screen.h" #ifdef HWRENDER #include "win_dll.h" // loading the render DLL #include "../hardware/hw_drv.h" // calling driver init & shutdown #include "../hardware/hw_main.h" // calling HWR module init & shutdown #endif // ------- // Globals // ------- // this is the CURRENT rendermode!! very important: used by w_wad, and much other code rendermode_t rendermode = render_soft; // synchronize page flipping with screen refresh consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; static consvar_t cv_stretch = {"stretch", "On", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; boolean highcolor; static BOOL bDIBMode; // means we are using DIB instead of DirectDraw surfaces static BITMAPINFO* bmiMain = NULL; static HDC hDCMain = NULL; // ----------------- // Video modes stuff // ----------------- #define MAX_EXTRA_MODES 36 static vmode_t extra_modes[MAX_EXTRA_MODES] = {{NULL, NULL, 0, 0, 0, 0, 0, 0, NULL, NULL, 0}}; static char names[MAX_EXTRA_MODES][10]; static int numvidmodes; // total number of DirectDraw display modes static vmode_t *pvidmodes; // start of videomodes list. static vmode_t *pcurrentmode; // the current active videomode. static BOOL bWinParm; static int WINAPI VID_SetWindowedDisplayMode(viddef_t *lvid, vmode_t *pcurrentmode); // this holds description of the startup video mode, // the resolution is 320x200, windowed on the desktop #define NUMSPECIALMODES 1 static char winmode1[] ="320x200W"; // W to make sure it's the windowed mode static vmode_t specialmodes[NUMSPECIALMODES] = { { NULL, winmode1, // hehe 320, 200, //(200.0/320.0)*(320.0/240.0), 320, 1, // rowbytes, bytes per pixel 1, 2, // windowed (TRUE), numpages NULL, VID_SetWindowedDisplayMode, 0 } }; // ------ // Protos // ------ static void VID_Command_NumModes_f(void); static void VID_Command_ModeInfo_f(void); static void VID_Command_ModeList_f(void); static void VID_Command_Mode_f(void); static int WINAPI VID_SetDirectDrawMode(viddef_t *lvid, vmode_t *pcurrentmode); static vmode_t *VID_GetModePtr(int modenum); static void VID_Init(void); static BOOL VID_FreeAndAllocVidbuffer(viddef_t *lvid); // ----------------- // I_StartupGraphics // Initialize video mode, setup dynamic screen size variables, // and allocate screens. // ----------------- void I_StartupGraphics(void) { if (graphics_started) return; #ifdef HWRENDER if (M_CheckParm("-opengl")) rendermode = render_opengl; else rendermode = render_soft; #endif if (dedicated) rendermode = render_none; else VID_Init(); // register exit code for graphics I_AddExitFunc(I_ShutdownGraphics); if (!dedicated) graphics_started = true; } // ------------------ // I_ShutdownGraphics // Close the screen, restore previous video mode. // ------------------ void I_ShutdownGraphics(void) { if (!graphics_started) return; CONS_Printf("I_ShutdownGraphics()\n"); //FreeConsole(); // release windowed startup stuff if (hDCMain) { ReleaseDC(hWndMain, hDCMain); hDCMain = NULL; } if (bmiMain) { GlobalFree(bmiMain); bmiMain = NULL; } #ifdef HWRENDER if (rendermode != render_soft) { HWR_Shutdown(); // free stuff from the hardware renderer HWD.pfnShutdown(); // close 3d card display Shutdown3DDriver(); // free the driver DLL } #endif // free the last video mode screen buffers if (vid.buffer) { GlobalFree(vid.buffer); vid.buffer = NULL; } #ifdef HWRENDER if (rendermode == render_soft) #endif CloseDirectDraw(); graphics_started = false; } // -------------- // I_UpdateNoBlit // -------------- void I_UpdateNoBlit(void) { // what is this? } // -------------- // I_FinishUpdate // -------------- void I_FinishUpdate(void) { int i; if (rendermode == render_none) return; // display a graph of ticrate if (cv_ticrate.value) SCR_DisplayTicRate(); // if (bDIBMode) { // paranoia if (!hDCMain || !bmiMain || !vid.buffer) return; // main game loop, still in a window (-win parm) SetDIBitsToDevice(hDCMain, 0, 0, 320, 200, 0, 0, 0, 200, vid.buffer, bmiMain, DIB_RGB_COLORS); } else #ifdef HWRENDER if (rendermode != render_soft) HWD.pfnFinishUpdate(cv_vidwait.value); else #endif { // DIRECT DRAW // copy virtual screen to real screen // can fail when not active (alt-tab) if (LockScreen()) { /// \todo use directX blit here!!? a blit might use hardware with access /// to main memory on recent hardware, and software blit of directX may be /// optimized for p2 or mmx?? VID_BlitLinearScreen(screens[0], ScreenPtr, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, ScreenPitch); UnlockScreen(); // swap screens ScreenFlip(cv_vidwait.value); } } } // // This is meant to be called only by CONS_Printf() while game startup // void I_LoadingScreen(LPCSTR msg) { RECT rect; // paranoia if (!hDCMain || !bmiMain || !vid.buffer) return; GetClientRect(vid.WndParent, &rect); SetDIBitsToDevice(hDCMain, 0, 0, 320, 200, 0, 0, 0, 200, vid.buffer, bmiMain, DIB_RGB_COLORS); if (msg) { if (rect.bottom - rect.top > 32) rect.top = rect.bottom - 32; // put msg on bottom of window SetBkMode(hDCMain, TRANSPARENT); SetTextColor(hDCMain, RGB(0x00, 0x00, 0x00)); DrawTextA(hDCMain, msg, -1, &rect, DT_WORDBREAK|DT_CENTER); } } // ------------ // I_ReadScreen // ------------ void I_ReadScreen(UINT8 *scr) { // DEBUGGING if (rendermode != render_soft) I_Error("I_ReadScreen: called while in non-software mode"); VID_BlitLinearScreen(screens[0], scr, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes); } // ------------ // I_SetPalette // ------------ void I_SetPalette(RGBA_t *palette) { int i; if (bDIBMode) { // set palette in RGBQUAD format, NOT THE SAME ORDER as PALETTEENTRY, grmpf! RGBQUAD *pColors; pColors = (RGBQUAD *)((char *)bmiMain + bmiMain->bmiHeader.biSize); ZeroMemory(pColors, sizeof (RGBQUAD)*256); for (i = 0; i < 256; i++, pColors++, palette++) { pColors->rgbRed = palette->s.red; pColors->rgbGreen = palette->s.green; pColors->rgbBlue = palette->s.blue; } } else #ifdef HWRENDER if (rendermode == render_soft) #endif { PALETTEENTRY mainpal[256]; // this clears the 'flag' for each color in palette ZeroMemory(mainpal, sizeof mainpal); // set palette in PALETTEENTRY format for (i = 0; i < 256; i++, palette++) { mainpal[i].peRed = palette->s.red; mainpal[i].peGreen = palette->s.green; mainpal[i].peBlue = palette->s.blue; } SetDDPalette(mainpal); // set DirectDraw palette } } // // return number of video modes in pvidmodes list // INT32 VID_NumModes(void) { return numvidmodes - NUMSPECIALMODES; //faB: dont accept the windowed mode 0 } // return a video mode number from the dimensions // returns any available video mode if the mode was not found INT32 VID_GetModeForSize(INT32 w, INT32 h) { vmode_t *pv = pvidmodes; int modenum = 0; // skip the special modes so that it finds only fullscreen modes for (; pv && modenum < NUMSPECIALMODES; pv = pv->pnext, ++modenum); for (; pv; pv = pv->pnext, ++modenum) if (pv->width == (unsigned)w && pv->height == (unsigned)h) return modenum; // if not found, return the first mode available, // preferably a full screen mode (all modes after the 'specialmodes') if (numvidmodes > NUMSPECIALMODES) return NUMSPECIALMODES; // use first full screen mode return 0; // no fullscreen mode, use windowed mode } // // Enumerate DirectDraw modes available // static int nummodes = 0; static BOOL GetExtraModesCallback(int width, int height, int bpp) { CONS_Printf("mode %d x %d x %d bpp\n", width, height, bpp); // skip all unwanted modes if (highcolor && bpp != 15) goto skip; if (!highcolor && bpp != 8) goto skip; if (bpp > 16 || width > MAXVIDWIDTH || height > MAXVIDHEIGHT) goto skip; // check if we have space for this mode if (nummodes >= MAX_EXTRA_MODES) { CONS_Printf("mode skipped (too many)\n"); return FALSE; } // store mode info extra_modes[nummodes].pnext = &extra_modes[nummodes+1]; memset(names[nummodes], 0, 10); snprintf(names[nummodes], 9, "%dx%d", width, height); extra_modes[nummodes].name = names[nummodes]; extra_modes[nummodes].width = width; extra_modes[nummodes].height = height; // exactly, the current FinishUdpate() gets the rowbytes itself after locking the video buffer // so for now we put anything here extra_modes[nummodes].rowbytes = width; extra_modes[nummodes].windowed = false; extra_modes[nummodes].misc = 0; // unused extra_modes[nummodes].pextradata = NULL; extra_modes[nummodes].setmode = VID_SetDirectDrawMode; extra_modes[nummodes].numpages = 2; // double-buffer (but this value is unused) extra_modes[nummodes].bytesperpixel = (bpp+1)>>3; nummodes++; skip: return TRUE; } // // Collect info about DirectDraw display modes we use // static inline void VID_GetExtraModes(void) { nummodes = 0; EnumDirectDrawDisplayModes(GetExtraModesCallback); // add the extra modes (not 320x200) at the start of the mode list (if there are any) if (nummodes) { extra_modes[nummodes-1].pnext = NULL; pvidmodes = &extra_modes[0]; numvidmodes += nummodes; } } // --------------- // WindowMode_Init // Add windowed modes to the start of the list, // mode 0 is used for windowed console startup (works on all computers with no DirectX) // --------------- static void WindowMode_Init(void) { specialmodes[NUMSPECIALMODES-1].pnext = pvidmodes; pvidmodes = &specialmodes[0]; numvidmodes += NUMSPECIALMODES; } // ************************************************************************************* // VID_Init // Initialize Video modes subsystem // ************************************************************************************* static void VID_Init(void) { vmode_t *pv; int iMode; // if '-win' is specified on the command line, do not add DirectDraw modes bWinParm = M_CheckParm("-win"); COM_AddCommand("vid_nummodes", VID_Command_NumModes_f); COM_AddCommand("vid_modeinfo", VID_Command_ModeInfo_f); COM_AddCommand("vid_modelist", VID_Command_ModeList_f); COM_AddCommand("vid_mode", VID_Command_Mode_f); CV_RegisterVar(&cv_vidwait); CV_RegisterVar(&cv_stretch); // setup the videmodes list, // note that mode 0 must always be VGA mode 0x13 pvidmodes = pcurrentmode = NULL; numvidmodes = 0; // store the main window handle in viddef struct SetWindowPos(hWndMain, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE|SWP_NOSENDCHANGING|SWP_NOSIZE|SWP_NOMOVE); vid.WndParent = hWndMain; vid.buffer = NULL; // we startup in windowed mode using DIB bitmap // we will use DirectDraw when switching fullScreen and entering main game loop bDIBMode = TRUE; bAppFullScreen = FALSE; #ifdef HWRENDER // initialize the appropriate display device if (rendermode != render_soft) { const char *drvname = NULL; switch (rendermode) { case render_opengl: drvname = "r_opengl.dll"; break; default: I_Error("Unknown hardware render mode"); } // load the DLL if (drvname && Init3DDriver(drvname)) { int hwdversion = HWD.pfnGetRenderVersion(); if (hwdversion != VERSION) CONS_Printf("WARNING: This r_opengl version is not supported, use it at your own risk.\n"); // perform initialisations HWD.pfnInit(I_Error); // get available display modes for the device HWD.pfnGetModeList(&pvidmodes, &numvidmodes); } else { switch (rendermode) { case render_opengl: I_Error("Error initializing OpenGL"); default: break; } rendermode = render_soft; } } if (rendermode == render_soft) #endif if (!bWinParm) { if (!CreateDirectDrawInstance()) I_Error("Error initializing DirectDraw"); // get available display modes for the device VID_GetExtraModes(); } // the game boots in 320x200 standard VGA, but // we need a highcolor mode to run the game in highcolor if (highcolor && !numvidmodes) I_Error("Cannot run in highcolor - No 15bit highcolor DirectX video mode found."); // add windowed mode at the start of the list, very important! WindowMode_Init(); if (!numvidmodes) I_Error("No display modes available."); // DEBUG for (iMode = 0, pv = pvidmodes; pv; pv = pv->pnext, iMode++) CONS_Printf("#%02d: %dx%dx%dbpp (desc: '%s')\n", iMode, pv->width, pv->height, pv->bytesperpixel, pv->name); // set the startup screen in a window VID_SetMode(0); } // -------------------------- // VID_SetWindowedDisplayMode // Display the startup 320x200 console screen into a window on the desktop, // switching to fullscreen display only when we will enter the main game loop. // - we can display error message boxes for startup errors // - we can set the last used resolution only once, when entering the main game loop // -------------------------- static int WINAPI VID_SetWindowedDisplayMode(viddef_t *lvid, vmode_t *pcurrentmode) { int iScrWidth, iScrHeight, iWinWidth, iWinHeight; pcurrentmode = NULL; #ifdef DEBUG CONS_Printf("VID_SetWindowedDisplayMode()\n"); #endif lvid->u.numpages = 1; // not used lvid->direct = NULL; // DOS remains lvid->buffer = NULL; // allocate screens if (!VID_FreeAndAllocVidbuffer(lvid)) return -1; // lvid->buffer should be NULL here! bmiMain = (void *)GlobalAlloc(GPTR, sizeof (BITMAPINFO) + (sizeof (RGBQUAD)*256)); if (!bmiMain) I_Error("VID_SWDM(): No mem"); // setup a BITMAPINFO to allow copying our video buffer to the desktop, // with color conversion as needed bmiMain->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); bmiMain->bmiHeader.biWidth = lvid->width; bmiMain->bmiHeader.biHeight= -(lvid->height); bmiMain->bmiHeader.biPlanes = 1; bmiMain->bmiHeader.biBitCount = 8; bmiMain->bmiHeader.biCompression = BI_RGB; // center window on the desktop iScrWidth = GetSystemMetrics(SM_CXFULLSCREEN); iScrHeight = GetSystemMetrics(SM_CYFULLSCREEN); iWinWidth = lvid->width; iWinWidth += GetSystemMetrics(SM_CXFIXEDFRAME) * 2; iWinHeight = lvid->height; iWinHeight += GetSystemMetrics(SM_CYCAPTION); iWinHeight += GetSystemMetrics(SM_CYFIXEDFRAME) * 2; if (devparm) MoveWindow(hWndMain, (iScrWidth - iWinWidth), (iScrHeight - iWinHeight), iWinWidth, iWinHeight, TRUE); else MoveWindow(hWndMain, (iScrWidth - iWinWidth)>>1, (iScrHeight - iWinHeight)>>1, iWinWidth, iWinHeight, TRUE); SetFocus(hWndMain); ShowWindow(hWndMain, SW_SHOW); hDCMain = GetDC(hWndMain); if (!hDCMain) I_Error("VID_SWDM(): GetDC FAILED"); return 1; } // ======================================================================== // Returns a vmode_t from the video modes list, given a video mode number. // ======================================================================== vmode_t *VID_GetModePtr(int modenum) { vmode_t *pv; pv = pvidmodes; if (!pv) I_Error("VID_error: No video mode found\n"); while (modenum--) { pv = pv->pnext; if (!pv) I_Error("VID_error: Mode not available\n"); } return pv; } // // return the name of a video mode // const char *VID_GetModeName(INT32 modenum) { return (VID_GetModePtr(modenum))->name; } // ======================================================================== // Sets a video mode // ======================================================================== INT32 VID_SetMode(INT32 modenum) { int stat; vmode_t *pnewmode; vmode_t *poldmode; if (dedicated) return 0; CONS_Printf("VID_SetMode(%d)\n", modenum); // if mode 0 (windowed) we must not be fullscreen already, // if other mode, check it is not mode 0 and existing if (modenum >= numvidmodes) { if (!pcurrentmode) modenum = 0; // revert to the default base vid mode else I_Error("Unknown video mode: %d\n", modenum); } else if (bAppFullScreen && modenum < NUMSPECIALMODES) I_Error("Tried to switch from fullscreen back to windowed mode %d\n", modenum); pnewmode = VID_GetModePtr(modenum); // dont switch to the same display mode if (pnewmode == pcurrentmode) return 1; // initialize the new mode poldmode = pcurrentmode; pcurrentmode = pnewmode; // initialize vidbuffer size for setmode vid.width = pcurrentmode->width; vid.height = pcurrentmode->height; vid.rowbytes = pcurrentmode->rowbytes; vid.bpp = pcurrentmode->bytesperpixel; if (modenum) // if not 320x200 windowed mode, it's actually a hack { if (rendermode == render_opengl) { // don't accept depth < 16 for OpenGL mode (too much ugly) if (cv_scr_depth.value < 16) CV_SetValue(&cv_scr_depth, 16); vid.bpp = cv_scr_depth.value/8; vid.u.windowed = (bWinParm || !cv_fullscreen.value); pcurrentmode->bytesperpixel = vid.bpp; pcurrentmode->windowed = vid.u.windowed; } } stat = (*pcurrentmode->setmode)(&vid, pcurrentmode); if (stat == -1) I_Error("Not enough mem for VID_SetMode\n"); else if (stat == -2) I_Error("Couldn't set video mode because it failed the test\n"); else if (stat == -3) I_Error("Couldn't set video mode because it failed the change?\n"); else if (!stat) I_Error("Couldn't set video mode %d (%dx%d %d bits)\n", modenum, vid.width, vid.height, (vid.bpp*8));// hardware could not setup mode else CONS_Printf(M_GetText("Mode changed to %d (%s)\n"), modenum, pcurrentmode->name); vid.modenum = modenum; // tell game engine to recalc all tables and realloc buffers based on new values vid.recalc = 1; if (modenum < NUMSPECIALMODES) { // we are in startup windowed mode bAppFullScreen = FALSE; bDIBMode = TRUE; } else { // we switch to fullscreen bAppFullScreen = TRUE; bDIBMode = FALSE; #ifdef HWRENDER if (rendermode != render_soft) { // purge all patch graphics stored in software format //Z_FreeTags (PU_PURGELEVEL, PU_PURGELEVEL+100); HWR_Startup(); } #endif } I_RestartSysMouse(); return 1; } // ======================================================================== // Free the video buffer of the last video mode, // allocate a new buffer for the video mode to set. // ======================================================================== static BOOL VID_FreeAndAllocVidbuffer(viddef_t *lvid) { const DWORD vidbuffersize = (lvid->width * lvid->height * lvid->bpp * NUMSCREENS); // free allocated buffer for previous video mode if (lvid->buffer) GlobalFree(lvid->buffer); // allocate & clear the new screen buffer lvid->buffer = GlobalAlloc(GPTR, vidbuffersize); if (!lvid->buffer) return FALSE; ZeroMemory(lvid->buffer, vidbuffersize); #ifdef DEBUG CONS_Printf("VID_FreeAndAllocVidbuffer done, vidbuffersize: %x\n",vidbuffersize); #endif return TRUE; } // ======================================================================== // Set video mode routine for DirectDraw display modes // Out: 1 ok, // 0 hardware could not set mode, // -1 no mem // ======================================================================== static int WINAPI VID_SetDirectDrawMode(viddef_t *lvid, vmode_t *pcurrentmode) { pcurrentmode = NULL; #ifdef DEBUG CONS_Printf("VID_SetDirectDrawMode...\n"); #endif // DD modes do double-buffer page flipping, but the game engine doesn't need this.. lvid->u.numpages = 2; // release ddraw surfaces etc.. ReleaseChtuff(); // clean up any old vid buffer lying around, alloc new if needed if (!VID_FreeAndAllocVidbuffer(lvid)) return -1; // no mem // should clear video mem here // note use lvid->bpp instead of 8...will this be needed? will we support other than 256color // in software ? if (!InitDirectDrawe(hWndMain, lvid->width, lvid->height, 8, TRUE)) // TRUE currently always full screen return 0; // could not set mode // this is NOT used with DirectDraw modes, game engine should never use this directly // but rather render to memory bitmap buffer lvid->direct = NULL; if (!cv_stretch.value && (float)vid.width/vid.height != ((float)BASEVIDWIDTH/BASEVIDHEIGHT)) vid.height = (int)(vid.width * ((float)BASEVIDHEIGHT/BASEVIDWIDTH));// Adjust the height to match return 1; } // ======================================================================== // VIDEO MODE CONSOLE COMMANDS // ======================================================================== // vid_nummodes // static void VID_Command_NumModes_f(void) { CONS_Printf(M_GetText("%d video mode(s) available(s)\n"), VID_NumModes()); } // vid_modeinfo // static void VID_Command_ModeInfo_f(void) { vmode_t *pv; int modenum; if (COM_Argc() != 2) modenum = vid.modenum; // describe the current mode else modenum = atoi(COM_Argv(1)); // the given mode number if (modenum >= VID_NumModes() || modenum < NUMSPECIALMODES) // don't accept the windowed mode 0 { CONS_Printf(M_GetText("Video mode not present\n")); return; } pv = VID_GetModePtr(modenum); CONS_Printf("%s\n", VID_GetModeName(modenum)); CONS_Printf(M_GetText("width: %d\nheight: %d\n"), pv->width, pv->height); if (rendermode == render_soft) CONS_Printf(M_GetText("bytes per scanline: %d\nbytes per pixel: %d\nnumpages: %d\n"), pv->rowbytes, pv->bytesperpixel, pv->numpages); } // vid_modelist // static void VID_Command_ModeList_f(void) { int i, nummodes; const char *pinfo; vmode_t *pv; nummodes = VID_NumModes(); for (i = NUMSPECIALMODES; i <= nummodes; i++) { pv = VID_GetModePtr(i); pinfo = VID_GetModeName(i); if (pv->bytesperpixel == 1) CONS_Printf("%d: %s\n", i, pinfo); else CONS_Printf("%d: %s (hicolor)\n", i, pinfo); } } // vid_mode // static void VID_Command_Mode_f(void) { int modenum; if (COM_Argc() != 2) { CONS_Printf(M_GetText("vid_mode : set video mode, current video mode %i\n"), vid.modenum); return; } modenum = atoi(COM_Argv(1)); if (modenum >= VID_NumModes() || modenum < NUMSPECIALMODES) // don't accept the windowed mode 0 CONS_Printf(M_GetText("Video mode not present\n")); else setmodeneeded = modenum + 1; // request vid mode change }