Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8398) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8399) @@ -1,1005 +1,998 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/app_hooks.h" #include "lib/input.h" #include "lib/lockfree.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/external_libraries/sdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/cursor.h" #include "lib/res/sound/snd_mgr.h" #include "lib/sysdep/cursor.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #if OS_WIN #include "lib/sysdep/os/win/wversion.h" #endif #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Loader.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/StringConvert.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "ps/i18n.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/ParticleEngine.h" #include "graphics/TerrainTextureManager.h" #include "renderer/Renderer.h" #include "renderer/VertexBufferManager.h" #include "maths/MathUtil.h" #include "simulation2/Simulation2.h" #include "scripting/ScriptingHost.h" #include "scripting/ScriptGlue.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptStats.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "graphics/scripting/JSInterface_Camera.h" #include "graphics/scripting/JSInterface_LightEnv.h" #include "ps/scripting/JSInterface_Console.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/ScriptFunctions.h" #include "sound/JSI_Sound.h" #include "network/NetServer.h" #include "network/NetClient.h" #include "ps/Pyrogenesis.h" // psSetLogDir #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #if !(OS_WIN || OS_MACOSX) // assume all other platforms use X11 for wxWidgets #define MUST_INIT_X11 1 #include #else #define MUST_INIT_X11 0 #endif #include ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); #define LOG_CATEGORY L"gamesetup" bool g_DoRenderGui = true; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(int quality) { int q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn(L"SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { CStrW i18n_description = I18n::translate(pending_task); JSString* js_desc = StringConvert::wstring_to_jsstring(g_ScriptingHost.getContext(), i18n_description); g_ScriptingHost.SetGlobal("g_Progress", INT_TO_JSVAL(percent)); g_ScriptingHost.SetGlobal("g_LoadDescription", STRING_TO_JSVAL(js_desc)); g_GUI->SendEventToAll("progress"); } void Render() { MICROLOG(L"begin frame"); ogl_WarnIfError(); CStr skystring = "255 0 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring, skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); } ogl_WarnIfError(); // set up overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); ogl_WarnIfError(); // Temp GUI message GeeTODO PROFILE_START("render gui"); if(g_DoRenderGui) g_GUI->Draw(); PROFILE_END("render gui"); ogl_WarnIfError(); // Particle Engine Updating CParticleEngine::GetInstance()->UpdateEmitters(); ogl_WarnIfError(); // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Added -- glEnable(GL_TEXTURE_2D); // -- GL glLoadIdentity(); PROFILE_START("render console"); g_Console->Render(); PROFILE_END("render console"); ogl_WarnIfError(); PROFILE_START("render logger"); g_Logger->Render(); PROFILE_END("render logger"); ogl_WarnIfError(); // Profile information PROFILE_START("render profiling"); g_ProfileViewer.RenderProfile(); PROFILE_END("render profiling"); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) CStrW cursorName = g_CursorName; if (cursorName.empty()) cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y); else cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y); // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); MICROLOG(L"end frame"); g_Renderer.EndFrame(); ogl_WarnIfError(); } static void RegisterJavascriptInterfaces() { // maths JSI_Vector3D::init(); // graphics JSI_Camera::init(); JSI_LightEnv::init(); CGameView::ScriptingInit(); // renderer CRenderer::ScriptingInit(); // sound JSI_Sound::ScriptingInit(); // ps JSI_Console::init(); // GUI CGUI::ScriptingInit(); GuiScriptingInit(g_ScriptingHost.GetScriptInterface()); } static void InitScripting() { TIMER(L"InitScripting"); // Create the scripting host. This needs to be done before the GUI is created. // [7ms] new ScriptingHost; RegisterJavascriptInterfaces(); } #if 0 // disabled because the file cache doesn't work (http://trac.wildfiregames.com/ticket/611) static size_t OperatingSystemFootprint() { #if OS_WIN switch(wversion_Number()) { case WVERSION_2K: case WVERSION_XP: return 150; case WVERSION_XP64: return 200; default: // don't warn about newer Windows versions case WVERSION_VISTA: return 300; case WVERSION_7: return 250; } #else return 200; #endif } static size_t ChooseCacheSize() { // (all sizes in MiB) const size_t total = os_cpu_MemorySize(); const size_t available = os_cpu_MemoryAvailable(); debug_assert(total >= available); const size_t inUse = total-available; size_t os = OperatingSystemFootprint(); debug_assert(total >= os/2); size_t apps = (inUse > os)? (inUse - os) : 0; size_t game = 300; size_t cache = 200; // plenty of memory if(os + apps + game + cache < total) { cache = total - os - apps - game; } else // below min-spec { // assume kernel+other apps will be swapped out os = 9*os/10; apps = 2*apps/3; game = 3*game/4; if(os + apps + game + cache < total) cache = total - os - apps - game; else { cache = 50; debug_printf(L"Warning: memory size (%d MiB, %d used) is rather low\n", total, available); } } // total data is currently about 500 MiB, and not all of it // is used at once, so don't use more than that. // (this also ensures the byte count will fit in size_t and // avoids using up too much address space) cache = std::min(cache, (size_t)500); debug_printf(L"Cache: %d (total: %d; available: %d)\n", cache, total, available); return cache*MiB; } #else static size_t ChooseCacheSize() { return 32*MiB; } #endif static void InitVfs(const CmdLineArgs& args) { TIMER(L"InitVfs"); const Paths paths(args); fs::wpath logs(paths.Logs()); CreateDirectories(logs, 0700); psSetLogDir(logs); // desired location for crashlog is now known. update AppHooks ASAP // (particularly before the following error-prone operations): AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); g_VFS = CreateVfs(cacheSize); g_VFS->Mount(L"screenshots/", paths.Data()/L"screenshots/"); const fs::wpath readonlyConfig = paths.RData()/L"config/"; g_VFS->Mount(L"config/", readonlyConfig); if(readonlyConfig != paths.Config()) g_VFS->Mount(L"config/", paths.Config()); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) std::vector mods = args.GetMultiple("mod"); mods.push_back("public"); if(!args.Has("onlyPublicFiles")) mods.push_back("internal"); fs::wpath modArchivePath(paths.Cache()/L"mods"); fs::wpath modLoosePath(paths.RData()/L"mods"); for (size_t i = 0; i < mods.size(); ++i) { size_t priority = i; size_t flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_MUST_EXIST; std::wstring modName (wstring_from_utf8(mods[i])); g_VFS->Mount(L"", AddSlash(modLoosePath/modName), flags, priority); g_VFS->Mount(L"", AddSlash(modArchivePath/modName), flags, priority); } // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } static void InitPs(bool setup_gui, const CStrW& gui_page) { { // console TIMER(L"ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font(CONSOLE_FONT); g_Console->m_iFontHeight = font.GetLineSpacing(); g_Console->m_iFontWidth = font.GetCharacterWidth(L'C'); g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 7; } // language and hotkeys { TIMER(L"ps_lang_hotkeys"); std::string lang = "english"; CFG_GET_SYS_VAL("language", String, lang); I18n::LoadLanguage(lang.c_str()); LoadHotkeys(); } if (!setup_gui) { // We do actually need *some* kind of GUI loaded, so use the // (currently empty) Atlas one g_GUI->SwitchPage(L"page_atlas.xml", JSVAL_VOID); return; } // GUI uses VFS, so this must come after VFS init. g_GUI->SwitchPage(gui_page, JSVAL_VOID); // Warn nicely about missing S3TC support if (!ogl_tex_has_s3tc()) { #if !(OS_WIN || OS_MACOSX) bool isMesa = true; #else bool isMesa = false; #endif g_GUI->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI->GetScriptObject()), "s3tcWarning", isMesa); } } static void InitInput() { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(conInputHandler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(HotkeyInputHandler); // gui_handler needs to be registered after (i.e. called before!) the // hotkey handler so that input boxes can be typed in without // setting off hotkeys. in_add_handler(gui_handler); // must be registered after (called before) the GUI which relies on these globals in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y); // Unload the real language (since it depends on the scripting engine, // which is going to be killed later) and use the English fallback messages I18n::LoadLanguage(NULL); } static void InitRenderer() { TIMER(L"InitRenderer"); if(g_NoGLS3TC) ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); if(g_NoGLAutoMipmap) ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_NOFRAMEBUFFEROBJECT,g_NoGLFramebufferObject); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_FANCYWATER,g_FancyWater); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTerrainTextureManager; // create the material manager new CMaterialManager; MICROLOG(L"init renderer"); g_Renderer.Open(g_xres,g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; g_Renderer.SetViewport(vp); ColorActivateFastImpl(); } static void InitSDL() { MICROLOG(L"init sdl"); if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOG(CLogger::Error, LOG_CATEGORY, L"SDL library initialization failed: %hs", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } static void ShutdownSDL() { SDL_Quit(); sys_cursor_reset(); } void EndGame() { SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } void Shutdown(int UNUSED(flags)) { MICROLOG(L"Shutdown"); EndGame(); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost in_reset_handlers(); // destroy actor related stuff TIMER_BEGIN(L"shutdown actor stuff"); delete &g_MaterialManager; TIMER_END(L"shutdown actor stuff"); // destroy terrain related stuff TIMER_BEGIN(L"shutdown TexMan"); delete &g_TexMan; TIMER_END(L"shutdown TexMan"); // destroy renderer TIMER_BEGIN(L"shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END(L"shutdown Renderer"); tex_codec_unregister_all(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); TIMER_BEGIN(L"shutdown ScriptingHost"); delete &g_ScriptingHost; TIMER_END(L"shutdown ScriptingHost"); TIMER_BEGIN(L"shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END(L"shutdown ConfigDB"); // Really shut down the i18n system. Any future calls // to translate() will crash. TIMER_BEGIN(L"shutdown I18N"); I18n::Shutdown(); TIMER_END(L"shutdown I18N"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN(L"resource modules"); snd_shutdown(); g_VFS.reset(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); file_stats_dump(); TIMER_END(L"resource modules"); TIMER_BEGIN(L"shutdown misc"); timer_DisplayClientTotals(); CNetHost::Deinitialize(); SAFE_DELETE(g_ScriptStatsTable); // should be last, since the above use them SAFE_DELETE(g_Logger); delete &g_Profiler; delete &g_ProfileViewer; TIMER_END(L"shutdown misc"); } void EarlyInit() { MICROLOG(L"EarlyInit"); // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); debug_SetThreadName("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add(L"TIMER"); debug_filter_add(L"HRT"); cpu_ConfigureFloatingPoint(); timer_LatchStartTime(); // Because we do GL calls from a secondary thread, Xlib needs to // be told to support multiple threads safely. // This is needed for Atlas, but we have to call it before any other // Xlib functions (e.g. the ones used when drawing the main menu // before launching Atlas) #if MUST_INIT_X11 int status = XInitThreads(); if (status == 0) debug_printf(L"Error enabling thread-safety via XInitThreads\n"); #endif // Initialise the low-quality rand function srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } static bool Autostart(const CmdLineArgs& args); void Init(const CmdLineArgs& args, int flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; MICROLOG(L"Init"); h_mgr_init(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. MICROLOG(L"init vfs"); InitVfs(args); // This must come after VFS init, which sets the current directory // (required for finding our output log files). g_Logger = new CLogger; // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time // on anything else.) if (args.Has("dumpSchema")) { CSimulation2 sim(NULL, NULL); sim.LoadDefaultScripts(); std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc); f << sim.GenerateSchema(); std::cout << "Generated entity.rng\n"; exit(0); } // Call LoadLanguage(NULL) to initialize the I18n system, but // without loading an actual language file - translate() will // just show the English key text, which is better than crashing // from a null pointer when attempting to translate e.g. error messages. // Real languages can only be loaded when the scripting system has // been initialised. // // this uses LOG and must therefore come after CLogger init. MICROLOG(L"init i18n"); I18n::LoadLanguage(NULL); // override ah_translate with our i18n code. AppHooks hooks = {0}; hooks.translate = psTranslate; hooks.translate_free = psTranslateFree; app_hooks_update(&hooks); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); CNetHost::Initialize(); if(setup_vmode) InitSDL(); new CProfileViewer; new CProfileManager; // before any script code g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); MICROLOG(L"init scripting"); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CONFIG_Init(args); if (setup_vmode) { if (!g_VideoMode.InitSDL()) throw PSERROR_System_VmodeFailed(); // abort startup SDL_WM_SetCaption("0 A.D.", "0 A.D."); } tex_codec_register_all(); const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file SetTextureQuality(quality); // needed by ogl_tex to detect broken gfx card/driver combos, // but takes a while due to WMI startup, so make it optional. if(!g_Quickstart) gfx_detect(); ogl_WarnIfError(); if(!g_Quickstart) { WriteSystemInfo(); // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } if(g_DisableAudio) { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); // (must come after SetVideoMode, since it calls ogl_Init) const char* missing = ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", NULL); if(missing) { wchar_t buf[500]; swprintf_s(buf, ARRAY_SIZE(buf), L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card.", missing ); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar")) { DEBUG_DISPLAY_ERROR( L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer." L" Shadows are not available and overall graphics quality might suffer." L" You are advised to try installing newer drivers and/or upgrade your graphics card."); g_Shadows = false; } - // enable/disable VSync - // note: "GL_EXT_SWAP_CONTROL" is "historical" according to dox. -#if OS_WIN - if(ogl_HaveExtension("WGL_EXT_swap_control")) - pwglSwapIntervalEXT(g_VSync? 1 : 0); -#endif - ogl_WarnIfError(); InitRenderer(); InitInput(); ogl_WarnIfError(); if (!Autostart(args)) { const bool setup_gui = ((flags & INIT_NO_GUI) == 0); InitPs(setup_gui, L"page_pregame.xml"); } } void RenderGui(bool RenderingState) { g_DoRenderGui = RenderingState; } // Network autostart: class AutostartNetServer : public CNetServer { public: AutostartNetServer(const CStr& map, int maxPlayers) : CNetServer(), m_NeedsStart(false), m_NumPlayers(0), m_MaxPlayers(maxPlayers) { CScriptValRooted attrs; GetScriptInterface().Eval("({})", attrs); GetScriptInterface().SetProperty(attrs.get(), "map", std::string(map), false); UpdateGameAttributes(attrs); } protected: virtual void OnAddPlayer() { m_NumPlayers++; debug_printf(L"# player joined (got %d, need %d)\n", (int)m_NumPlayers, (int)m_MaxPlayers); if (m_NumPlayers >= m_MaxPlayers) m_NeedsStart = true; // delay until next Poll, so the new player has been fully processed } virtual void OnRemovePlayer() { debug_warn(L"client left?!"); m_NumPlayers--; } virtual void Poll() { if (m_NeedsStart) { StartGame(); m_NeedsStart = false; } CNetServer::Poll(); } private: bool m_NeedsStart; size_t m_NumPlayers; size_t m_MaxPlayers; }; static bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=mapname -- single-player * -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players * -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1 */ CStr autostartMap = args.Get("autostart"); if (autostartMap.empty()) return false; g_Game = new CGame(); if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml"); size_t maxPlayers = 2; if (args.Has("autostart-players")) maxPlayers = args.Get("autostart-players").ToUInt(); g_NetServer = new AutostartNetServer(autostartMap, maxPlayers); bool ok = g_NetServer->SetupConnection(); debug_assert(ok); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml"); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc bool ok = g_NetClient->SetupConnection(args.Get("autostart-ip")); debug_assert(ok); } else { CScriptValRooted attrs; g_Game->GetSimulation2()->GetScriptInterface().Eval("({})", attrs); g_Game->GetSimulation2()->GetScriptInterface().SetProperty(attrs.get(), "map", std::string(autostartMap), false); g_Game->SetPlayerID(1); g_Game->StartGame(attrs); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); InitPs(true, L"page_session_new.xml"); } return true; } Index: ps/trunk/source/lib/sysdep/os/win/wsdl.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wsdl.cpp (revision 8398) +++ ps/trunk/source/lib/sysdep/os/win/wsdl.cpp (revision 8399) @@ -1,1473 +1,1489 @@ /* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * emulate SDL on Windows. */ #include "precompiled.h" #include "lib/external_libraries/sdl.h" #if CONFIG2_WSDL #include #include #include #include #include "lib/sysdep/os/win/win.h" #include // _beginthreadex #include // message crackers #include "lib/posix/posix_pthread.h" #include "lib/module_init.h" #include "lib/sysdep/os/win/wutil.h" #include "lib/sysdep/os/win/winit.h" #include "lib/sysdep/os/win/wmi.h" // for SDL_GetVideoInfo #if MSC_VERSION #pragma comment(lib, "user32.lib") #pragma comment(lib, "gdi32.lib") #endif #include "lib/ogl.h" // needed to pull in the delay-loaded opengl32.dll WINIT_REGISTER_LATE_INIT(wsdl_Init); WINIT_REGISTER_EARLY_SHUTDOWN(wsdl_Shutdown); // in fullscreen mode, i.e. not windowed. // video mode will be restored when app is deactivated. static bool fullscreen; // the app is shutting down. // if set, ignore further Windows messages for clean shutdown. static bool is_quitting; // when a SDL_VIDEORESIZE event is received, SDL wants the application // to call SDL_SetVideoMode, which would trigger another SDL_VIDEORESIZE. // we prevent this by skipping a subsequent resize event for every // call to SDL_SetVideoMode. static bool skipResize; static HWND g_hWnd = (HWND)INVALID_HANDLE_VALUE; static HDC g_hDC = (HDC)INVALID_HANDLE_VALUE; // needed by gamma code //---------------------------------------------------------------------------- // gamma class GammaRamp { public: GammaRamp() : m_hasChanged(false) { } bool Change(float gamma_r, float gamma_g, float gamma_b) { // get current ramp (once) so we can later restore it. if(!m_hasChanged) { debug_assert(wutil_IsValidHandle(g_hDC)); if(!GetDeviceGammaRamp(g_hDC, m_original)) return false; } Compute(gamma_r, m_changed+0*256); Compute(gamma_g, m_changed+1*256); Compute(gamma_b, m_changed+2*256); if(!Upload(m_changed)) return false; m_hasChanged = true; return true; } void Latch() { if(m_hasChanged) Upload(m_changed); } void RestoreOriginal() { if(m_hasChanged) Upload(m_original); } private: static void Compute(float gamma, u16* ramp) { // assume identity if invalid if(gamma <= 0.0f) gamma = 1.0f; // identity: special-case to make sure we get exact values if(gamma == 1.0f) { for(u16 i = 0; i < 256; i++) ramp[i] = u16(i << 8); return; } for(int i = 0; i < 256; i++) { const double val = pow(i/255.0, (double)gamma); const double clamped = std::max(0.0, std::min(val, 1.0-DBL_EPSILON)); ramp[i] = u16_from_double(clamped); } debug_assert(ramp[0] == 0); debug_assert(ramp[255] == 0xFFFF); } bool Upload(u16* ramps) { WinScopedPreserveLastError s; SetLastError(0); debug_assert(wutil_IsValidHandle(g_hDC)); BOOL ok = SetDeviceGammaRamp(g_hDC, ramps); // on multi-monitor NVidia systems, the first call after a reboot // fails, but subsequent ones succeed. // see http://icculus.org/pipermail/quake3-bugzilla/2009-October/001316.html if(ok == FALSE) { ok = SetDeviceGammaRamp(g_hDC, ramps); debug_assert(ok); } return (ok == TRUE); } bool m_hasChanged; // values are 8.8 fixed point u16 m_original[3*256]; u16 m_changed[3*256]; }; static GammaRamp gammaRamp; // note: any component gamma = 0 is assumed to be identity. int SDL_SetGamma(float r, float g, float b) { return gammaRamp.Change(r, g, b)? 0 : -1; } //---------------------------------------------------------------------------- // window //---------------------------------------------------------------------------- static DWORD wnd_ChooseWindowStyle(bool fullscreen, HWND previousWindow = (HWND)INVALID_HANDLE_VALUE) { DWORD windowStyle = fullscreen? WS_POPUP : WS_POPUPWINDOW|WS_CAPTION|WS_MINIMIZEBOX; windowStyle |= WS_VISIBLE; windowStyle |= WS_CLIPCHILDREN|WS_CLIPSIBLINGS; // MSDN SetPixelFormat says this is required if(!fullscreen) // windowed { // support resizing windowStyle |= WS_SIZEBOX|WS_MAXIMIZEBOX; // remember the previous maximized state // (else subsequent attempts to maximize will fail) if(wutil_IsValidHandle(previousWindow)) { const DWORD previousWindowState = GetWindowLongW(previousWindow, GWL_STYLE); windowStyle |= (previousWindowState & WS_MAXIMIZE); } } return windowStyle; } // @param w,h value-return (in: desired, out: actual pixel count) static void wnd_UpdateWindowDimensions(DWORD windowStyle, int& w, int& h) { // Calculate the size of the outer window, so that the client area has // the desired dimensions. RECT r; r.left = r.top = 0; r.right = w; r.bottom = h; if (AdjustWindowRectEx(&r, windowStyle, FALSE, 0)) { w = r.right - r.left; h = r.bottom - r.top; } } static LRESULT CALLBACK wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static HWND wnd_CreateWindow(int w, int h) { // (create new window every time (instead of once at startup), because // pixel format isn't supposed to be changed more than once) // app instance. // returned by GetModuleHandle and used in keyboard hook and window creation. const HINSTANCE hInst = GetModuleHandle(0); // register window class WNDCLASSW wc; memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = wndproc; wc.lpszClassName = L"WSDL"; wc.hInstance = hInst; ATOM class_atom = RegisterClassW(&wc); if(!class_atom) { debug_assert(0); // SDL_SetVideoMode: RegisterClass failed return 0; } const DWORD windowStyle = wnd_ChooseWindowStyle(fullscreen); wnd_UpdateWindowDimensions(windowStyle, w, h); // note: you can override the hardcoded window name via SDL_WM_SetCaption. return CreateWindowExW(WS_EX_APPWINDOW, (LPCWSTR)(uintptr_t)class_atom, L"wsdl", windowStyle, 0, 0, w, h, 0, 0, hInst, 0); } //---------------------------------------------------------------------------- // video //---------------------------------------------------------------------------- static DEVMODE dm; // current video mode static HGLRC hGLRC = (HGLRC)INVALID_HANDLE_VALUE; -static int depth_bits = 24; // depth buffer size; set via SDL_GL_SetAttribute +// set via SDL_GL_SetAttribute: +static int depthBufferBits = 24; +static int vsyncEnabled = 1; + +int SDL_GL_SetAttribute(SDL_GLattr attr, int value) +{ + switch(attr) + { + case SDL_GL_DEPTH_SIZE: + depthBufferBits = value; + break; + + case SDL_GL_SWAP_CONTROL: + vsyncEnabled = value; + break; + } + + return 0; +} + // check if resolution needs to be changed static bool video_NeedsChange(int w, int h, int cur_w, int cur_h, bool fullscreen) { // invalid: keep current settings if(w <= 0 || h <= 0) return false; // higher resolution mode needed if(w > cur_w || h > cur_h) return true; // fullscreen requested and not exact same mode set if(fullscreen && (w != cur_w || h != cur_h)) return true; return false; } -int SDL_GL_SetAttribute(SDL_GLattr attr, int value) -{ - if(attr == SDL_GL_DEPTH_SIZE) - depth_bits = value; - - return 0; -} - - static void video_SetPixelFormat(HDC g_hDC, int bpp) { const DWORD dwFlags = PFD_SUPPORT_OPENGL|PFD_DRAW_TO_WINDOW|PFD_DOUBLEBUFFER; BYTE cColourBits = (BYTE)bpp; BYTE cAlphaBits = 0; if(bpp == 32) { cColourBits = 24; cAlphaBits = 8; } const BYTE cAccumBits = 0; - const BYTE cDepthBits = (BYTE)depth_bits; + const BYTE cDepthBits = (BYTE)depthBufferBits; const BYTE cStencilBits = 0; const BYTE cAuxBuffers = 0; PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, // version dwFlags, PFD_TYPE_RGBA, cColourBits, 0, 0, 0, 0, 0, 0, // c*Bits, c*Shift are unused cAlphaBits, 0, // cAlphaShift is unused cAccumBits, 0, 0, 0, 0, // cAccum*Bits are unused cDepthBits, cStencilBits, cAuxBuffers, PFD_MAIN_PLANE, 0, 0, 0, 0 // bReserved, dw*Mask are unused }; // note: the GDI pixel format functions require opengl32.dll to be loaded. // a deadlock on the next line is probably due to VLD's LdrLoadDll hook. const int pf = ChoosePixelFormat(g_hDC, &pfd); debug_assert(pf >= 1); WARN_IF_FALSE(SetPixelFormat(g_hDC, pf, &pfd)); } // set video mode width x height : bpp (or leave unchanged if already adequate). // w = h = bpp = 0 => no change. SDL_Surface* SDL_SetVideoMode(int w, int h, int bpp, Uint32 flags) { WinScopedPreserveLastError s; // OpenGL and GDI fullscreen = (flags & SDL_FULLSCREEN) != 0; // get current mode settings memset(&dm, 0, sizeof(dm)); dm.dmSize = sizeof(dm); EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm); const int cur_w = (int)dm.dmPelsWidth, cur_h = (int)dm.dmPelsHeight; // independent of resolution; app must always get bpp it wants dm.dmBitsPerPel = bpp; dm.dmFields = DM_BITSPERPEL; if(video_NeedsChange(w,h, cur_w,cur_h, fullscreen)) { dm.dmPelsWidth = (DWORD)w; dm.dmPelsHeight = (DWORD)h; dm.dmFields |= DM_PELSWIDTH|DM_PELSHEIGHT; } // the (possibly changed) mode will be (re)set at next WM_ACTIVATE skipResize = true; if(g_hWnd == (HWND)INVALID_HANDLE_VALUE) { g_hWnd = wnd_CreateWindow(w, h); if(!wutil_IsValidHandle(g_hWnd)) return 0; g_hDC = GetDC(g_hWnd); video_SetPixelFormat(g_hDC, bpp); hGLRC = wglCreateContext(g_hDC); if(!hGLRC) return 0; if(!wglMakeCurrent(g_hDC, hGLRC)) return 0; } else // update the existing window { const DWORD windowStyle = wnd_ChooseWindowStyle(fullscreen, g_hWnd); wnd_UpdateWindowDimensions(windowStyle, w, h); UINT swp_flags = SWP_FRAMECHANGED|SWP_NOZORDER|SWP_NOACTIVATE; if(!fullscreen) // windowed: preserve the top-left corner swp_flags |= SWP_NOMOVE; WARN_IF_FALSE(SetWindowLongW(g_hWnd, GWL_STYLE, windowStyle)); WARN_IF_FALSE(SetWindowPos(g_hWnd, 0, 0, 0, w, h, swp_flags)); if(fullscreen) { ShowWindow(g_hWnd, SW_RESTORE); ChangeDisplaySettings(&dm, CDS_FULLSCREEN); } else { ChangeDisplaySettings(0, 0); // don't ShowWindow with SW_MINIMIZE (we just want to update) } } // get the actual updated window size and return it static SDL_Surface screen; RECT rect; WARN_IF_FALSE(GetClientRect(g_hWnd, &rect)); screen.w = rect.right; screen.h = rect.bottom; + // (required for ogl_HaveExtension, but callers should also invoke + // ogl_Init in case the real SDL is being used.) + ogl_Init(); + if(ogl_HaveExtension("WGL_EXT_swap_control") && pwglSwapIntervalEXT) + pwglSwapIntervalEXT(vsyncEnabled); + return &screen; } static void video_Shutdown() { if(fullscreen) { LONG status = ChangeDisplaySettings(0, 0); debug_assert(status == DISP_CHANGE_SUCCESSFUL); } if(wutil_IsValidHandle(hGLRC)) { WARN_IF_FALSE(wglMakeCurrent(0, 0)); WARN_IF_FALSE(wglDeleteContext(hGLRC)); hGLRC = (HGLRC)INVALID_HANDLE_VALUE; } g_hWnd = (HWND)INVALID_HANDLE_VALUE; g_hDC = (HDC)INVALID_HANDLE_VALUE; } void SDL_GL_SwapBuffers() { SwapBuffers(g_hDC); } SDL_VideoInfo* SDL_GetVideoInfo() { static SDL_VideoInfo video_info; if(video_info.video_mem == 0) { WmiMap videoAdapter; if(wmi_GetClass(L"Win32_VideoController", videoAdapter) == INFO::OK) { VARIANT vTotalMemory = videoAdapter[L"AdapterRAM"]; video_info.video_mem = vTotalMemory.lVal; } } return &video_info; } SDL_Surface* SDL_GetVideoSurface() { return 0; } //---------------------------------------------------------------------------- // event queue // note: we only use winit to redirect stdout; this queue won't be used // before _cinit. typedef std::queue Queue; static Queue g_queue; static void QueueEvent(const SDL_Event& ev) { g_queue.push(ev); } static bool DequeueEvent(SDL_Event& ev) { if(g_queue.empty()) return false; ev = g_queue.front(); g_queue.pop(); return true; } //---------------------------------------------------------------------------- // keyboard // note: keysym.unicode is only returned for SDL_KEYDOWN, and is otherwise 0. static void QueueKeyEvent(Uint8 type, SDLKey sdlk, WCHAR unicode_char) { SDL_Event ev; ev.type = type; ev.key.keysym.sym = sdlk; ev.key.keysym.unicode = (Uint16)unicode_char; QueueEvent(ev); } static Uint8 keys[SDLK_LAST]; static SDLKey g_SDLKeyForVK[256]; // g_SDLKeyForVK[vk] == SDLK static void key_Init() { // Map the VK keysyms for(int i = 0; i < ARRAY_SIZE(g_SDLKeyForVK); i++) g_SDLKeyForVK[i] = SDLK_UNKNOWN; g_SDLKeyForVK[VK_BACK] = SDLK_BACKSPACE; g_SDLKeyForVK[VK_TAB] = SDLK_TAB; g_SDLKeyForVK[VK_CLEAR] = SDLK_CLEAR; g_SDLKeyForVK[VK_RETURN] = SDLK_RETURN; g_SDLKeyForVK[VK_PAUSE] = SDLK_PAUSE; g_SDLKeyForVK[VK_ESCAPE] = SDLK_ESCAPE; g_SDLKeyForVK[VK_SPACE] = SDLK_SPACE; g_SDLKeyForVK[VK_OEM_7] = SDLK_QUOTE; g_SDLKeyForVK[VK_OEM_COMMA] = SDLK_COMMA; g_SDLKeyForVK[VK_OEM_MINUS] = SDLK_MINUS; g_SDLKeyForVK[VK_OEM_PERIOD] = SDLK_PERIOD; g_SDLKeyForVK[VK_OEM_2] = SDLK_SLASH; g_SDLKeyForVK[VK_OEM_1] = SDLK_SEMICOLON; g_SDLKeyForVK[VK_OEM_PLUS] = SDLK_EQUALS; g_SDLKeyForVK[VK_OEM_4] = SDLK_LEFTBRACKET; g_SDLKeyForVK[VK_OEM_5] = SDLK_BACKSLASH; g_SDLKeyForVK[VK_OEM_6] = SDLK_RIGHTBRACKET; g_SDLKeyForVK[VK_OEM_3] = SDLK_BACKQUOTE; g_SDLKeyForVK[VK_OEM_8] = SDLK_BACKQUOTE; // winuser.h guarantees A..Z and 0..9 match their ASCII values: const int VK_0 = '0'; for(int i = 0; i < 10; i++) g_SDLKeyForVK[VK_0+i] = (SDLKey)(SDLK_0+i); const int VK_A = 'A'; for(int i = 0; i < 26; i++) g_SDLKeyForVK[VK_A+i] = (SDLKey)(SDLK_a+i); g_SDLKeyForVK[VK_DELETE] = SDLK_DELETE; for(int i = 0; i < 10; i++) g_SDLKeyForVK[VK_NUMPAD0+i] = (SDLKey)(SDLK_KP0+i); g_SDLKeyForVK[VK_DECIMAL] = SDLK_KP_PERIOD; g_SDLKeyForVK[VK_DIVIDE] = SDLK_KP_DIVIDE; g_SDLKeyForVK[VK_MULTIPLY] = SDLK_KP_MULTIPLY; g_SDLKeyForVK[VK_SUBTRACT] = SDLK_KP_MINUS; g_SDLKeyForVK[VK_ADD] = SDLK_KP_PLUS; g_SDLKeyForVK[VK_UP] = SDLK_UP; g_SDLKeyForVK[VK_DOWN] = SDLK_DOWN; g_SDLKeyForVK[VK_RIGHT] = SDLK_RIGHT; g_SDLKeyForVK[VK_LEFT] = SDLK_LEFT; g_SDLKeyForVK[VK_INSERT] = SDLK_INSERT; g_SDLKeyForVK[VK_HOME] = SDLK_HOME; g_SDLKeyForVK[VK_END] = SDLK_END; g_SDLKeyForVK[VK_PRIOR] = SDLK_PAGEUP; g_SDLKeyForVK[VK_NEXT] = SDLK_PAGEDOWN; for(int i = 0; i < 12; i++) g_SDLKeyForVK[VK_F1+i] = (SDLKey)(SDLK_F1+i); g_SDLKeyForVK[VK_NUMLOCK] = SDLK_NUMLOCK; g_SDLKeyForVK[VK_CAPITAL] = SDLK_CAPSLOCK; g_SDLKeyForVK[VK_SCROLL] = SDLK_SCROLLOCK; g_SDLKeyForVK[VK_RSHIFT] = SDLK_RSHIFT; g_SDLKeyForVK[VK_LSHIFT] = SDLK_LSHIFT; g_SDLKeyForVK[VK_SHIFT] = SDLK_LSHIFT; // XXX: Not quite g_SDLKeyForVK[VK_RCONTROL] = SDLK_RCTRL; g_SDLKeyForVK[VK_LCONTROL] = SDLK_LCTRL; g_SDLKeyForVK[VK_CONTROL] = SDLK_LCTRL; // XXX: Not quite g_SDLKeyForVK[VK_RMENU] = SDLK_RALT; g_SDLKeyForVK[VK_LMENU] = SDLK_LALT; g_SDLKeyForVK[VK_MENU] = SDLK_LALT; // XXX: Not quite g_SDLKeyForVK[VK_RWIN] = SDLK_RSUPER; g_SDLKeyForVK[VK_LWIN] = SDLK_LSUPER; g_SDLKeyForVK[VK_HELP] = SDLK_HELP; #ifdef VK_PRINT g_SDLKeyForVK[VK_PRINT] = SDLK_PRINT; #endif g_SDLKeyForVK[VK_SNAPSHOT] = SDLK_PRINT; g_SDLKeyForVK[VK_CANCEL] = SDLK_BREAK; g_SDLKeyForVK[VK_APPS] = SDLK_MENU; } static inline SDLKey SDLKeyFromVK(int vk) { if(!(0 <= vk && vk < 256)) { debug_assert(0); // invalid vk return SDLK_UNKNOWN; } return g_SDLKeyForVK[vk]; } static void key_ResetAll() { SDL_Event spoofed_up_event; spoofed_up_event.type = SDL_KEYUP; spoofed_up_event.key.keysym.unicode = 0; for(int i = 0; i < ARRAY_SIZE(keys); i++) { if(keys[i]) { spoofed_up_event.key.keysym.sym = (SDLKey)i; QueueEvent(spoofed_up_event); } } } static LRESULT OnKey(HWND UNUSED(hWnd), UINT vk, BOOL fDown, int UNUSED(cRepeat), UINT flags) { // TODO Mappings for left/right modifier keys // TODO Modifier statekeeping const SDLKey sdlk = SDLKeyFromVK(vk); if(sdlk != SDLK_UNKNOWN) keys[sdlk] = (Uint8)fDown; if(!fDown) QueueKeyEvent(SDL_KEYUP, sdlk, 0); else { // note: flags is HIWORD(lParam) from WM_KEYDOWN, which includes // scancode. ToUnicode also uses its bit 15 to determine if the // key is currently pressed. const UINT scancode = flags; u8 key_states[256]; GetKeyboardState(key_states); WCHAR wchars[8]; int output_count = ToUnicode(vk, scancode, key_states, wchars, ARRAY_SIZE(wchars), 0); // translation succeeded; queue each produced character if(output_count > 0) { for(int i = 0; i < output_count; i++) QueueKeyEvent(SDL_KEYDOWN, sdlk, wchars[i]); } // dead-char; do nothing else if(output_count == -1) { } // translation failed; just generate a regular (non-unicode) event else if(output_count == 0) QueueKeyEvent(SDL_KEYDOWN, sdlk, 0); else UNREACHABLE; } return 0; } Uint8* SDL_GetKeyState(int* num_keys) { if(num_keys) *num_keys = SDLK_LAST; return keys; } // always on (we don't care about the extra overhead) int SDL_EnableUNICODE(int UNUSED(enable)) { return 1; } //---------------------------------------------------------------------------- // app activation enum SdlActivationType { LOSE = 0, GAIN = 1 }; static inline void QueueActiveEvent(SdlActivationType type, size_t changed_app_state) { // SDL says this event is not generated when the window is created, // but skipping the first event may confuse things. SDL_Event ev; ev.type = SDL_ACTIVEEVENT; ev.active.state = (u8)changed_app_state; ev.active.gain = (u8)((type == GAIN)? 1 : 0); QueueEvent(ev); } // SDL_APP* bitflags indicating whether we are active. // note: responsibility for yielding lies with SDL apps - // they control the main loop. static Uint8 app_state; static void active_ChangeState(SdlActivationType type, Uint8 changed_app_state) { Uint8 old_app_state = app_state; if(type == GAIN) app_state = Uint8(app_state | changed_app_state); else app_state = Uint8(app_state & ~changed_app_state); // generate an event - but only if the given state flags actually changed. if((old_app_state & changed_app_state) != (app_state & changed_app_state)) QueueActiveEvent(type, changed_app_state); } static LRESULT OnActivate(HWND hWnd, UINT state, HWND UNUSED(hWndActDeact), BOOL fMinimized) { SdlActivationType type; Uint8 changed_app_state; // went active and not minimized if(state != WA_INACTIVE && !fMinimized) { type = GAIN; changed_app_state = SDL_APPINPUTFOCUS|SDL_APPACTIVE; // grab keyboard focus (we previously had DefWindowProc do this). SetFocus(hWnd); gammaRamp.Latch(); if(fullscreen) { ShowWindow(g_hWnd, SW_RESTORE); ChangeDisplaySettings(&dm, CDS_FULLSCREEN); } } // deactivated (Alt+Tab out) or minimized else { type = LOSE; changed_app_state = SDL_APPINPUTFOCUS; if(fMinimized) changed_app_state |= SDL_APPACTIVE; key_ResetAll(); gammaRamp.RestoreOriginal(); if(fullscreen) { ChangeDisplaySettings(0, 0); ShowWindow(g_hWnd, SW_MINIMIZE); } } active_ChangeState(type, changed_app_state); return 0; } Uint8 SDL_GetAppState() { return app_state; } static void QueueQuitEvent() { SDL_Event ev; ev.type = SDL_QUIT; QueueEvent(ev); } //---------------------------------------------------------------------------- // mouse // background: there are several types of coordinates. // - screen coords are relative to the primary desktop and may therefore be // negative on multi-monitor systems (e.g. if secondary monitor is left of // primary). they are prefixed with screen_*. // - "client" coords are simply relative to the parent window's origin and // can also be negative (e.g. in the window's NC area). // these are prefixed with client_*. // - "idealized" coords are what the app sees. these range from 0 to // windowDimensions-1. they are returned by mouse_GetCoords and have no prefix. static void QueueMouseEvent(int x, int y) { SDL_Event ev; ev.type = SDL_MOUSEMOTION; debug_assert(unsigned(x|y) <= USHRT_MAX); ev.motion.x = (Uint16)x; ev.motion.y = (Uint16)y; QueueEvent(ev); } static void QueueButtonEvent(int button, int state, int x, int y) { SDL_Event ev; ev.type = Uint8((state == SDL_PRESSED)? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP); ev.button.button = (u8)button; ev.button.state = (u8)state; debug_assert(unsigned(x|y) <= USHRT_MAX); ev.button.x = (Uint16)x; ev.button.y = (Uint16)y; QueueEvent(ev); } static int mouse_x, mouse_y; // generate a mouse move message and update our notion of the mouse position. // x, y are client pixel coordinates. // notes: // - does not actually move the OS cursor; // - called from mouse_Update and SDL_WarpMouse. static void mouse_UpdatePosition(int x, int y) { // nothing to do if it hasn't changed since last time if(mouse_x == x && mouse_y == y) return; mouse_x = x; mouse_y = y; QueueMouseEvent(x, y); } static POINT mouse_ScreenFromClient(int client_x, int client_y) { POINT screen_pt; screen_pt.x = (LONG)client_x; screen_pt.y = (LONG)client_y; WARN_IF_FALSE(ClientToScreen(g_hWnd, &screen_pt)); return screen_pt; } // get idealized client coordinates or return false if outside our window. static bool mouse_GetCoords(int screen_x, int screen_y, int& x, int& y) { debug_assert(wutil_IsValidHandle(g_hWnd)); POINT screen_pt; screen_pt.x = (LONG)screen_x; screen_pt.y = (LONG)screen_y; POINT client_pt; { // note: MapWindowPoints has a really stupid interface, returning 0 // on failure or if no shift was needed (i.e. window is fullscreen). // we must use GetLastError to detect error conditions. WinScopedPreserveLastError s; SetLastError(0); client_pt = screen_pt; // translated below const int ret = MapWindowPoints(HWND_DESKTOP, g_hWnd, &client_pt, 1); debug_assert(ret != 0 || GetLastError() == 0); } { RECT client_rect; WARN_IF_FALSE(GetClientRect(g_hWnd, &client_rect)); if(!PtInRect(&client_rect, client_pt)) return false; } if(WindowFromPoint(screen_pt) != g_hWnd) return false; x = client_pt.x; y = client_pt.y; debug_assert(x >= 0 && y >= 0); return true; } static void mouse_Update() { // window not created yet or already shut down. no sense reporting // mouse position, and bail now to avoid ScreenToClient failing. if(!wutil_IsValidHandle(g_hWnd)) return; // don't use DirectInput, because we want to respect the user's mouse // sensitivity settings. Windows messages are laggy, so query current // position directly. // note: GetCursorPos fails if the desktop is switched (e.g. after // pressing Ctrl+Alt+Del), which can be ignored. POINT screen_pt; if(!GetCursorPos(&screen_pt)) return; int x, y; if(mouse_GetCoords(screen_pt.x, screen_pt.y, x, y)) { active_ChangeState(GAIN, SDL_APPMOUSEFOCUS); mouse_UpdatePosition(x, y); } // moved outside of window else active_ChangeState(LOSE, SDL_APPMOUSEFOCUS); } static unsigned mouse_buttons; // (we define a new function signature since the windowsx.h message crackers // don't provide for passing uMsg) static LRESULT OnMouseButton(HWND UNUSED(hWnd), UINT uMsg, int client_x, int client_y, UINT UNUSED(flags)) { int button; int state; switch(uMsg) { case WM_LBUTTONDOWN: button = SDL_BUTTON_LEFT; state = SDL_PRESSED; break; case WM_LBUTTONUP: button = SDL_BUTTON_LEFT; state = SDL_RELEASED; break; case WM_RBUTTONDOWN: button = SDL_BUTTON_RIGHT; state = SDL_PRESSED; break; case WM_RBUTTONUP: button = SDL_BUTTON_RIGHT; state = SDL_RELEASED; break; case WM_MBUTTONDOWN: button = SDL_BUTTON_MIDDLE; state = SDL_PRESSED; break; case WM_MBUTTONUP: button = SDL_BUTTON_MIDDLE; state = SDL_RELEASED; break; NODEFAULT; } // mouse capture static int outstanding_press_events; if(state == SDL_PRESSED) { // grab mouse to ensure we get up events if(++outstanding_press_events > 0) SetCapture(g_hWnd); } else { // release after all up events received if(--outstanding_press_events <= 0) { ReleaseCapture(); outstanding_press_events = 0; } } // update button bitfield if(state == SDL_PRESSED) mouse_buttons |= SDL_BUTTON(button); else mouse_buttons &= ~SDL_BUTTON(button); const POINT screen_pt = mouse_ScreenFromClient(client_x, client_y); int x, y; if(mouse_GetCoords(screen_pt.x, screen_pt.y, x, y)) QueueButtonEvent(button, state, x, y); return 0; } // (note: this message is sent even if the cursor is outside our window) static LRESULT OnMouseWheel(HWND UNUSED(hWnd), int screen_x, int screen_y, int zDelta, UINT UNUSED(fwKeys)) { int x, y; if(mouse_GetCoords(screen_x, screen_y, x, y)) { int button = (zDelta < 0)? SDL_BUTTON_WHEELDOWN : SDL_BUTTON_WHEELUP; // SDL says this sends a down message followed by up. QueueButtonEvent(button, SDL_PRESSED, x, y); QueueButtonEvent(button, SDL_RELEASED, x, y); } return 0; // handled } Uint8 SDL_GetMouseState(int* x, int* y) { if(x) *x = (int)mouse_x; if(y) *y = (int)mouse_y; return (Uint8)mouse_buttons; } void SDL_WarpMouse(int x, int y) { // SDL interface provides for int, but the values should be // idealized client coords (>= 0) debug_assert(x >= 0 && y >= 0); mouse_UpdatePosition(x, y); const int client_x = x, client_y = y; const POINT screen_pt = mouse_ScreenFromClient(client_x, client_y); WARN_IF_FALSE(SetCursorPos(screen_pt.x, screen_pt.y)); } int SDL_ShowCursor(int toggle) { static int cursor_visible = SDL_ENABLE; if(toggle != SDL_QUERY) { // only call Windows ShowCursor if changing the visibility - // it maintains a counter. if(cursor_visible != toggle) { ShowCursor(toggle); cursor_visible = toggle; } } return cursor_visible; } SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode) { static SDL_GrabMode prevMode; if(mode == SDL_GRAB_QUERY) return prevMode; prevMode = mode; if(mode == SDL_GRAB_OFF) ClipCursor(0); else { RECT clientRect; WARN_IF_FALSE(GetClientRect(g_hWnd, &clientRect)); POINT upperLeft = { clientRect.left, clientRect.top }; WARN_IF_FALSE(ClientToScreen(g_hWnd, &upperLeft)); POINT lowerRight = { clientRect.right, clientRect.bottom }; WARN_IF_FALSE(ClientToScreen(g_hWnd, &lowerRight)); const RECT screenRect = { upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y }; WARN_IF_FALSE(ClipCursor(&screenRect)); } return mode; } //---------------------------------------------------------------------------- // video resizing/expose static bool ResizeEventEnabled(int clientWidth, int clientHeight) { // SDL_SetVideoMode causes this message, and relaying the // resize event to the app requires it to call // SDL_SetVideoMode again, so avoid infinite recursion. if(skipResize) { skipResize = false; return false; } // if fullscreen, interaction with other topmost windows causes // minimization and a spurious resize. however, the app only // expects resizing events if !fullscreen. if(fullscreen) return false; // this happens during minimization, which results in an // app-active event anyway, and the app might have // trouble with size=0. if(clientWidth == 0 && clientHeight == 0) return false; return true; } // note: this is called continuously during resizing. since SDL doesn't // discard any SDL_VIDEORESIZE events, the application must deal with // the flood (and only call SDL_SetVideoMode once a frame or similar). // note: SDL uses WM_WINDOWPOSCHANGING, which requires calling // GetClientRect and suffers from false alarms. static void OnSize(HWND UNUSED(hWnd), UINT UNUSED(state), int clientWidth, int clientHeight) { if(!ResizeEventEnabled(clientWidth, clientHeight)) return; SDL_Event ev; ev.type = SDL_VIDEORESIZE; ev.resize.w = clientWidth; ev.resize.h = clientHeight; QueueEvent(ev); } static BOOL OnEraseBkgnd(HWND UNUSED(hWnd), HDC UNUSED(hDC)) { SDL_Event ev; ev.type = SDL_VIDEOEXPOSE; QueueEvent(ev); // indicate we erased the background; PAINTSTRUCT.fErase will // later be FALSE. return TRUE; } //---------------------------------------------------------------------------- static LRESULT OnDestroy(HWND hWnd) { debug_assert(hWnd == g_hWnd); WARN_IF_FALSE(ReleaseDC(g_hWnd, g_hDC)); g_hDC = (HDC)INVALID_HANDLE_VALUE; g_hWnd = (HWND)INVALID_HANDLE_VALUE; QueueQuitEvent(); PostQuitMessage(0); #ifdef _DEBUG // see http://www.adrianmccarthy.com/blog/?p=51 // with WM_QUIT in the message queue, MessageBox will immediately // return IDABORT. to ensure any subsequent CRT error reports are // at least somewhat visible, we redirect them to debug output. _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); #endif return 0; } static LRESULT CALLBACK wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if(is_quitting) return DefWindowProcW(hWnd, uMsg, wParam, lParam); switch(uMsg) { case WM_PAINT: PAINTSTRUCT ps; BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); return 0; HANDLE_MSG(hWnd, WM_ERASEBKGND, OnEraseBkgnd); // prevent selecting menu in fullscreen mode case WM_NCHITTEST: if(fullscreen) return HTCLIENT; break; HANDLE_MSG(hWnd, WM_ACTIVATE, OnActivate); HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy); case WM_SYSCOMMAND: switch(wParam) { // prevent moving, sizing, screensaver, and power-off in fullscreen mode case SC_MOVE: case SC_SIZE: case SC_MAXIMIZE: case SC_MONITORPOWER: if(fullscreen) return 1; break; // Alt+F4 or system menu doubleclick/exit case SC_CLOSE: QueueQuitEvent(); break; } break; HANDLE_MSG(hWnd, WM_SYSKEYUP , OnKey); HANDLE_MSG(hWnd, WM_KEYUP , OnKey); HANDLE_MSG(hWnd, WM_SYSKEYDOWN, OnKey); HANDLE_MSG(hWnd, WM_KEYDOWN , OnKey); HANDLE_MSG(hWnd, WM_MOUSEWHEEL, OnMouseWheel); HANDLE_MSG(hWnd, WM_SIZE, OnSize); // (can't use message crackers: they do not provide for passing uMsg) case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: return OnMouseButton(hWnd, uMsg, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT)wParam); default: // can't call DefWindowProc here: some messages // are only conditionally 'grabbed' (e.g. NCHITTEST) break; } return DefWindowProcW(hWnd, uMsg, wParam, lParam); } void SDL_PumpEvents() { // rationale: we would like to reduce CPU usage automatically if // possible. blocking here until a message arrives would accomplish // that, but might potentially freeze the app too long. // instead, they should check active state and call SDL_Delay etc. // if our window is minimized. mouse_Update(); // polled MSG msg; while(PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) { DispatchMessageW(&msg); } } int SDL_PollEvent(SDL_Event* ev) { SDL_PumpEvents(); if(DequeueEvent(*ev)) return 1; return 0; } int SDL_PushEvent(SDL_Event* ev) { QueueEvent(*ev); return 0; } //----------------------------------------------------------------------------- // byte swapping // implement only if the header hasn't mapped SDL_Swap* to intrinsics #ifndef SDL_Swap16 u16 SDL_Swap16(const u16 x) { return (u16)(((x & 0xff) << 8) | (x >> 8)); } #endif #ifndef SDL_Swap32 u32 SDL_Swap32(const u32 x) { return (x << 24) | (x >> 24) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00); } #endif #ifndef SDL_Swap64 u64 SDL_Swap64(const u64 x) { const u32 lo = (u32)(x & 0xFFFFFFFF); const u32 hi = (u32)(x >> 32); u64 ret = SDL_Swap32(lo); ret <<= 32; // careful: must shift var of type u64, not u32 ret |= SDL_Swap32(hi); return ret; } #endif //----------------------------------------------------------------------------- // multithread support // semaphores // note: implementing these in terms of pthread sem_t doesn't help; // this wrapper is very close to the Win32 routines. static HANDLE HANDLE_from_sem(SDL_sem* s) { return (HANDLE)s; } static SDL_sem* sem_from_HANDLE(HANDLE h) { return (SDL_sem*)h; } SDL_sem* SDL_CreateSemaphore(int cnt) { HANDLE h = CreateSemaphore(0, cnt, std::numeric_limits::max(), 0); return sem_from_HANDLE(h); } void SDL_DestroySemaphore(SDL_sem* sem) { HANDLE h = HANDLE_from_sem(sem); CloseHandle(h); } int SDL_SemPost(SDL_sem* sem) { HANDLE h = HANDLE_from_sem(sem); return ReleaseSemaphore(h, 1, 0); } int SDL_SemWait(SDL_sem* sem) { HANDLE h = HANDLE_from_sem(sem); return WaitForSingleObject(h, INFINITE); } // threads // users don't need to allocate SDL_Thread variables, so type = void // API returns SDL_Thread*, which is the HANDLE value itself. // // we go through hoops to avoid type cast warnings; // a simple union { pthread_t; SDL_Thread* } yields "uninitialized" // warnings in VC2005, so we coerce values directly. cassert(sizeof(pthread_t) == sizeof(SDL_Thread*)); SDL_Thread* SDL_CreateThread(int (*func)(void*), void* param) { pthread_t thread = 0; if(pthread_create(&thread, 0, (void* (*)(void*))func, param) < 0) return 0; return *(SDL_Thread**)&thread; } int SDL_KillThread(SDL_Thread* thread) { pthread_cancel(*(pthread_t*)&thread); return 0; } //----------------------------------------------------------------------------- // misc API void SDL_WM_SetCaption(const char* title, const char* icon) { WARN_IF_FALSE(SetWindowTextA(g_hWnd, title)); // real SDL ignores this parameter, so we will follow suit. UNUSED2(icon); } u32 SDL_GetTicks() { return GetTickCount(); } void SDL_Delay(Uint32 ms) { Sleep(ms); } void* SDL_GL_GetProcAddress(const char* name) { return wglGetProcAddress(name); } //----------------------------------------------------------------------------- // init/shutdown // defend against calling SDL_Quit twice (GameSetup does this to work // around ATI driver breakage) static ModuleInitState initState; static LibError Init() { key_Init(); return INFO::OK; } static void Shutdown() { is_quitting = true; if(wutil_IsValidHandle(g_hDC)) gammaRamp.RestoreOriginal(); if(wutil_IsValidHandle(g_hWnd)) WARN_IF_FALSE(DestroyWindow(g_hWnd)); video_Shutdown(); } int SDL_Init(Uint32 UNUSED(flags)) { return (ModuleInit(&initState, Init) < 0)? -1 : 0; } void SDL_Quit() { ModuleShutdown(&initState, Shutdown); } static fs::wpath GetStdoutPathname() { // the current directory is unreliable, so use the full path to // the current executable. wchar_t pathnameEXE[MAX_PATH]; const DWORD charsWritten = GetModuleFileNameW(0, pathnameEXE, ARRAY_SIZE(pathnameEXE)); debug_assert(charsWritten); const fs::wpath path = fs::wpath(pathnameEXE).branch_path(); // add the EXE name to the filename to allow multiple executables // with their own redirections. (we can't use wutil_ExecutablePath // because it doesn't return the basename) std::wstring name = fs::basename(pathnameEXE); fs::wpath pathname(path/(name+L"_stdout.txt")); return pathname; } static void RedirectStdout() { // this process is apparently attached to a console, and users might be // surprised to find that we redirected the output to a file, so don't. if(wutil_IsValidHandle(GetStdHandle(STD_OUTPUT_HANDLE))) return; const fs::wpath pathname = GetStdoutPathname(); // ignore BoundsChecker warnings here. subsystem is set to "Windows" // to prevent the OS from opening a console on startup (ugly). // that means stdout isn't associated with a lowio handle; _close is // called with fd = -1. oh well, there's nothing we can do. FILE* f = 0; // (return value ignored - it indicates 'file already exists' even // if f is valid) (void)_wfreopen_s(&f, pathname.string().c_str(), L"wt", stdout); // executable directory (probably Program Files) is read-only for // non-Administrators. we can't pick another directory because // ah_log_dir might not be valid until the app's init has run, // nor should we pollute the (root) wutil_AppdataPath directory. // since stdout usually isn't critical and is seen if launching the // app from a console, just skip the redirection in this case. if(f == 0) return; #if CONFIG_PARANOIA // disable buffering, so that no writes are lost even if the program // crashes. only enabled in full debug mode because this is really slow! setvbuf(stdout, 0, _IONBF, 0); #endif } static LibError wsdl_Init() { // note: SDL does this in its WinMain hook. doing so in SDL_Init would be // too late (we might miss some output), so we use winit. // (this is possible because _cinit has already been called) RedirectStdout(); return INFO::OK; } static LibError wsdl_Shutdown() { // was redirected to stdout.txt; closing avoids a BoundsChecker warning. fclose(stdout); return INFO::OK; } #endif // #if CONFIG2_WSDL Index: ps/trunk/source/lib/sysdep/os/win/wsdl.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wsdl.h (revision 8398) +++ ps/trunk/source/lib/sysdep/os/win/wsdl.h (revision 8399) @@ -1,298 +1,298 @@ /* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * emulate SDL on Windows. */ #ifndef INCLUDED_WSDL #define INCLUDED_WSDL #include "lib/lib_api.h" #include "lib/byte_order.h" #include "SDL/SDL_keysym.h" typedef u8 Uint8; typedef u16 Uint16; typedef u32 Uint32; // SDL_Init flags #define SDL_INIT_VIDEO 0 #define SDL_INIT_AUDIO 0 #define SDL_INIT_TIMER 0 #define SDL_INIT_NOPARACHUTE 0 LIB_API int SDL_Init(Uint32 flags); LIB_API void SDL_Quit(); // // video // typedef enum { SDL_GL_DEPTH_SIZE, SDL_GL_DOUBLEBUFFER, // ignored - always double buffered - SDL_GL_SWAP_CONTROL // not implemented yet + SDL_GL_SWAP_CONTROL } SDL_GLattr; LIB_API int SDL_GL_SetAttribute(SDL_GLattr attr, int value); // SDL_SetVideoMode() flags #define SDL_OPENGL 0 #define SDL_FULLSCREEN 1 #define SDL_RESIZABLE 2 typedef struct { int w, h; } SDL_Surface; LIB_API SDL_Surface* SDL_SetVideoMode(int w, int h, int bpp, Uint32 flags); LIB_API SDL_Surface* SDL_GetVideoSurface(); typedef struct { int video_mem; } SDL_VideoInfo; LIB_API SDL_VideoInfo* SDL_GetVideoInfo(); LIB_API void* SDL_GL_GetProcAddress(const char*); LIB_API void SDL_GL_SwapBuffers(); // // threads / sync // typedef void SDL_sem; typedef void SDL_Thread; LIB_API u32 SDL_GetTicks(); LIB_API void SDL_Delay(u32 ms); LIB_API SDL_sem* SDL_CreateSemaphore(int cnt); LIB_API void SDL_DestroySemaphore(SDL_sem*); LIB_API int SDL_SemPost(SDL_sem*); LIB_API int SDL_SemWait(SDL_sem* sem); LIB_API SDL_Thread* SDL_CreateThread(int (*)(void*), void*); LIB_API int SDL_KillThread(SDL_Thread*); LIB_API void SDL_WarpMouse(int, int); enum ShowCursorToggle { SDL_DISABLE = 0, SDL_ENABLE = 1, SDL_QUERY = 2 }; LIB_API int SDL_ShowCursor(int toggle); LIB_API int SDL_SetGamma(float r, float g, float b); // // byte swapping // #define SDL_LIL_ENDIAN 1234 #define SDL_BIG_ENDIAN 4321 #define SDL_BYTEORDER SDL_LIL_ENDIAN #define SDL_Swap16 swap16 #define SDL_Swap32 swap32 #define SDL_Swap64 swap64 //----------------------------------------------------------------------------- // events //----------------------------------------------------------------------------- typedef struct { SDLKey sym; u16 unicode; } SDL_keysym; typedef struct { Uint8 type; SDL_keysym keysym; } SDL_KeyboardEvent; typedef struct { Uint8 type; u16 x, y; } SDL_MouseMotionEvent; typedef struct { Uint8 type; } SDL_QuitEvent; typedef struct { Uint8 type; } SDL_ExposeEvent; enum SDL_MouseButtonEvent_button { // to remain compatible with regular SDL, these values must not change! SDL_BUTTON_LEFT = 1, SDL_BUTTON_MIDDLE = 2, SDL_BUTTON_RIGHT = 3, SDL_BUTTON_WHEELUP = 4, SDL_BUTTON_WHEELDOWN = 5 }; #define SDL_BUTTON(b) (1u << (b-1)) #define SDL_BUTTON_LMASK SDL_BUTTON(SDL_BUTTON_LEFT) #define SDL_BUTTON_MMASK SDL_BUTTON(SDL_BUTTON_MIDDLE) #define SDL_BUTTON_RMASK SDL_BUTTON(SDL_BUTTON_RIGHT) enum SDL_MouseButtonEvent_state { SDL_RELEASED = 0, SDL_PRESSED = 1 }; typedef struct { Uint8 type; u8 button; u8 state; u16 x, y; } SDL_MouseButtonEvent; enum SDL_ActiveEvent_state { SDL_APPACTIVE = 1, SDL_APPMOUSEFOCUS = 2, SDL_APPINPUTFOCUS = 4 }; typedef struct { Uint8 type; u8 gain; u8 state; } SDL_ActiveEvent; typedef struct { Uint8 type; int w; int h; } SDL_ResizeEvent; typedef struct { Uint8 type; int code; void* data1; } SDL_UserEvent; enum SDL_Event_type { SDL_KEYDOWN, SDL_KEYUP, SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP, SDL_ACTIVEEVENT, SDL_QUIT, SDL_VIDEOEXPOSE, SDL_VIDEORESIZE, SDL_USEREVENT }; typedef union { Uint8 type; SDL_ActiveEvent active; SDL_KeyboardEvent key; SDL_MouseMotionEvent motion; SDL_MouseButtonEvent button; SDL_ResizeEvent resize; SDL_ExposeEvent expose; SDL_QuitEvent quit; SDL_UserEvent user; } SDL_Event; LIB_API int SDL_EnableUNICODE(int enable); LIB_API int SDL_WaitEvent(SDL_Event*); LIB_API int SDL_PollEvent(SDL_Event* ev); LIB_API int SDL_PushEvent(SDL_Event* ev); // // misc // enum SDL_GrabMode { SDL_GRAB_QUERY, SDL_GRAB_OFF, SDL_GRAB_ON }; LIB_API SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode); #define SDL_GetError() "" // from real SDL, but they're ignored anyway #define SDL_DEFAULT_REPEAT_DELAY 500 #define SDL_DEFAULT_REPEAT_INTERVAL 30 #define SDL_EnableKeyRepeat(delay, interval) LIB_API void SDL_WM_SetCaption(const char *title, const char *icon); LIB_API Uint8* SDL_GetKeyState(int* num_keys); LIB_API Uint8 SDL_GetMouseState(int* x, int* y); LIB_API Uint8 SDL_GetAppState(); #endif // #ifndef INCLUDED_WSDL