Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 25691) +++ ps/trunk/binaries/data/config/default.cfg (revision 25692) @@ -1,564 +1,567 @@ ; Global Configuration Settings ; ; ************************************************************** ; * DO NOT EDIT THIS FILE if you want personal customisations: * ; * create a text file called "local.cfg" instead, and copy * ; * the lines from this file that you want to change. * ; * * ; * If a setting is part of a section (for instance [hotkey]) * ; * you need to append the section name at the beginning of * ; * your custom line (for instance you need to write * ; * "hotkey.pause = Space" if you want to change the pausing * ; * hotkey to the spacebar). * ; * * ; * On Linux, create: * ; * $XDG_CONFIG_HOME/0ad/config/local.cfg * ; * (Note: $XDG_CONFIG_HOME defaults to ~/.config) * ; * * ; * On OS X, create: * ; * ~/Library/Application\ Support/0ad/config/local.cfg * ; * * ; * On Windows, create: * ; * %appdata%\0ad\config\local.cfg * ; * * ; ************************************************************** ; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.) windowed = false ; Show detailed tooltips (Unit stats) showdetailedtooltips = false ; Pause the game on window focus loss (Only applicable to single player mode) pauseonfocusloss = true ; Persist settings after leaving the game setup screen persistmatchsettings = true ; Default player name to use in multiplayer ; playername = "anonymous" ; Default server name or IP to use in multiplayer multiplayerserver = "127.0.0.1" ; Force a particular resolution. (If these are 0, the default is ; to keep the current desktop resolution in fullscreen mode or to ; use 1024x768 in windowed mode.) xres = 0 yres = 0 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth) bpp = 0 ; Preferred display (for multidisplay setups, only works with SDL 2.0) display = 0 +; Enable Hi-DPI where supported, currently working only for testing. +hidpi = false + ; Allows to force GL version for SDL forceglversion = false forceglprofile = "compatibility" ; Possible values: compatibility, core, es forceglmajorversion = 3 forceglminorversion = 3 ; Big screenshot tiles screenshot.tiles = 4 screenshot.tilewidth = 480 screenshot.tileheight = 270 ; Emulate right-click with Ctrl+Click on Mac mice macmouse = false ; System settings: ; if false, actors won't be rendered but anything entity will be. renderactors = true watereffects=true ; When disabled, force usage of the fixed pipeline water. This is faster, but really, really ugly. waterfancyeffects = false waterrealdepth = true waterrefraction = true waterreflection = true shadows = true shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) ; High values can crash the game when using a graphics card with low memory! shadowpcf = true shadowsfixed = false ; When enabled shadows are rendered only on the shadowsfixeddistance = 300.0 ; fixed distance and without swimming effect. texturequality = 5 ; Texture resolution and quality (0 - Lowest, 1 Very Low, 2 - Low, 3 - Medium, 4 - High, 5 - Very High, 6 - Ultra) vsync = false particles = true fog = true silhouettes = true showsky = true ; Uses a synchonized call to a GL driver to get an error state. Useful ; for a debugging of a system without GL_KHR_debug. gl.checkerrorafterswap = false ; Disable hardware cursors nohwcursor = false ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities ; fixed Only use OpenGL fixed function pipeline ; shader Use vertex/fragment shaders for transform and lighting where possible ; Using 'fixed' instead of 'default' may work around some graphics-related problems, ; but will reduce performance and features when a modern graphics card is available. renderpath = default ;;;;; EXPERIMENTAL ;;;;; ; Prefer GLSL shaders over ARB shaders. Allows fancier graphical effects. preferglsl = false ; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk gpuskinning = false ; Use smooth LOS interpolation smoothlos = false ; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath. postproc = false ; Use anti-aliasing techniques. antialiasing = "disabled" ; Use sharpening techniques. sharpening = "disabled" sharpness = 0.3 ; Quality used for actors. max_actor_quality=200 ; Whether or not actor variants are selected randomly, possible values are "full", "limited", "none". variant_diversity = "full" ; Quality level of shader effects (set to 10 to display all effects) materialmgr.quality = 2.0 ; Maximum distance to display parallax effect. Set to 0 to disable parallax. materialmgr.PARALLAX_DIST.max = 150 ; Maximum distance to display high quality parallax effect. materialmgr.PARALLAX_HQ_DIST.max = 75 ; Maximum distance to display very high quality parallax effect. Set to 30 to enable. materialmgr.PARALLAX_VHQ_DIST.max = 0 ;;;;;;;;;;;;;;;;;;;;;;;; ; Replace alpha-blending with alpha-testing, for performance experiments forcealphatest = false ; Color of the sky (in "r g b" format) skycolor = "0 0 0" [adaptivefps] session = 60 ; Throttle FPS in running games (prevents 100% CPU workload). menu = 60 ; Throttle FPS in menus only. [profiler2] server = "127.0.0.1" server.port = "8000" ; Use a free port on your machine. server.threads = "6" ; Enough for the browser's parallel connection limit [hotkey] ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+'. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS exit = "Ctrl+Break", "Super+Q", "Alt+F4" ; Exit to desktop cancel = Escape ; Close or cancel the current dialog box/popup confirm = Return ; Confirm the current command pause = Pause, "Shift+Space" ; Pause/unpause game screenshot = F2 ; Take PNG screenshot bigscreenshot = "Shift+F2" ; Take large BMP screenshot togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots wireframe = "Alt+Shift+W" ; Toggle wireframe mode silhouettes = "Alt+Shift+S" ; Toggle unit silhouettes ; > DIALOG HOTKEYS summary = "Ctrl+Tab" ; Toggle in-game summary lobby = "Alt+L" ; Show the multiplayer lobby in a dialog window. structree = "Alt+Shift+T" ; Show structure tree civinfo = "Alt+Shift+H" ; Show civilization info ; > CLIPBOARD CONTROLS copy = "Ctrl+C" ; Copy to clipboard paste = "Ctrl+V" ; Paste from clipboard cut = "Ctrl+X" ; Cut selected text and copy to the clipboard ; > CONSOLE SETTINGS console.toggle = BackQuote, F9 ; Open/close console ; > OVERLAY KEYS fps.toggle = "Alt+F" ; Toggle frame counter realtime.toggle = "Alt+T" ; Toggle current display of computer time timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter ceasefirecounter.toggle = "" ; Toggle ceasefire counter ; > HOTKEYS ONLY chat = Return ; Toggle chat window teamchat = "T" ; Toggle chat window in team chat mode privatechat = "L" ; Toggle chat window and select the previous private chat partner ; > QUICKSAVE quicksave = "Shift+F5" quickload = "Shift+F8" [hotkey.camera] reset = "R" ; Reset camera rotation to default. follow = "F" ; Follow the first unit in the selection rallypointfocus = "" ; Focus the camera on the rally point of the selected building zoom.in = Plus, NumPlus ; Zoom camera in (continuous control) zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) pan = MouseMiddle ; Enable scrolling by moving mouse left = A, LeftArrow ; Scroll or rotate left right = D, RightArrow ; Scroll or rotate right up = W, UpArrow ; Scroll or rotate up/forwards down = S, DownArrow ; Scroll or rotate down/backwards scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed [hotkey.camera.jump] 1 = F5 ; Jump to position N 2 = F6 3 = F7 4 = F8 ;5 = ;6 = ;7 = ;8 = ;9 = ;10 = [hotkey.camera.jump.set] 1 = "Ctrl+F5" ; Set jump position N 2 = "Ctrl+F6" 3 = "Ctrl+F7" 4 = "Ctrl+F8" ;5 = ;6 = ;7 = ;8 = ;9 = ;10 = [hotkey.profile] toggle = "F11" ; Enable/disable real-time profiler save = "Shift+F11" ; Save current profiler data to logs/profile.txt [hotkey.profile2] toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler [hotkey.selection] cancel = Esc ; Un-select all units and cancel building placement add = Shift ; Add units to selection militaryonly = Alt ; Add only military units to the selection nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection idleonly = "I" ; Select only idle units woundedonly = "O" ; Select only wounded units remove = Ctrl ; Remove units from selection idlebuilder = Semicolon ; Select next idle builder idleworker = Period, NumDecimal ; Select next idle worker idlewarrior = Slash, NumDivide ; Select next idle warrior idleunit = BackSlash ; Select next idle unit offscreen = Alt ; Include offscreen units in selection [hotkey.selection.group.add] 0 = "Shift+0", "Shift+Num0" 1 = "Shift+1", "Shift+Num1" 2 = "Shift+2", "Shift+Num2" 3 = "Shift+3", "Shift+Num3" 4 = "Shift+4", "Shift+Num4" 5 = "Shift+5", "Shift+Num5" 6 = "Shift+6", "Shift+Num6" 7 = "Shift+7", "Shift+Num7" 8 = "Shift+8", "Shift+Num8" 9 = "Shift+9", "Shift+Num9" [hotkey.selection.group.save] 0 = "Ctrl+0", "Ctrl+Num0" 1 = "Ctrl+1", "Ctrl+Num1" 2 = "Ctrl+2", "Ctrl+Num2" 3 = "Ctrl+3", "Ctrl+Num3" 4 = "Ctrl+4", "Ctrl+Num4" 5 = "Ctrl+5", "Ctrl+Num5" 6 = "Ctrl+6", "Ctrl+Num6" 7 = "Ctrl+7", "Ctrl+Num7" 8 = "Ctrl+8", "Ctrl+Num8" 9 = "Ctrl+9", "Ctrl+Num9" [hotkey.selection.group.select] 0 = 0, Num0 1 = 1, Num1 2 = 2, Num2 3 = 3, Num3 4 = 4, Num4 5 = 5, Num5 6 = 6, Num6 7 = 7, Num7 8 = 8, Num8 9 = 9, Num9 [hotkey.gamesetup] mapbrowser.open = "M" [hotkey.session] kill = Delete, Backspace ; Destroy selected units stop = "H" ; Stop the current action backtowork = "Y" ; The unit will go back to work unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected unloadturrets = "U" ; Unload turreted units. leaveturret = "U" ; Leave turret point. move = "" ; Modifier to move to a point instead of another action (e.g. gather) attack = Ctrl ; Modifier to attack instead of another action (e.g. capture) attackmove = Ctrl ; Modifier to attackmove when clicking on a point attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point garrison = Ctrl ; Modifier to garrison when clicking on building occupyturret = Ctrl ; Modifier to occupy a turret when clicking on a turret holder. autorallypoint = Ctrl ; Modifier to set the rally point on the building itself guard = "G" ; Modifier to escort/guard when clicking on unit/building patrol = "P" ; Modifier to patrol a unit repair = "J" ; Modifier to repair when clicking on building/mechanical unit queue = Shift ; Modifier to queue unit orders instead of replacing pushorderfront = "" ; Modifier to push unit orders to the front instead of replacing. orderone = Alt ; Modifier to order only one entity in selection. batchtrain = Shift ; Modifier to train units in batches massbarter = Shift ; Modifier to barter bunch of resources masstribute = Shift ; Modifier to tribute bunch of resources noconfirmation = Shift ; Do not ask confirmation when deleting a building/unit fulltradeswap = Shift ; Modifier to put the desired trade resource to 100% unloadtype = Shift ; Modifier to unload all units of type deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting rotate.cw = RightBracket ; Rotate building placement preview clockwise rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise snaptoedges = Ctrl ; Modifier to align new structures with nearby existing structure toggledefaultformation = "" ; Switch between null default formation and the last default formation used (defaults to "box") flare = K ; Modifier to send a flare to your allies flareactivate = "" ; Modifier to activate the mode to send a flare to your allies ; Overlays showstatusbars = Tab ; Toggle display of status bars devcommands.toggle = "Alt+D" ; Toggle developer commands panel highlightguarding = PageDown ; Toggle highlight of guarding units highlightguarded = PageUp ; Toggle highlight of guarded units diplomacycolors = "Alt+X" ; Toggle diplomacy colors toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures togglehealrange = "Alt+B" ; Toggle display of heal range overlays of selected units [hotkey.session.gui] toggle = "Alt+G" ; Toggle visibility of session GUI menu.toggle = "F10" ; Toggle in-game menu diplomacy.toggle = "Ctrl+H" ; Toggle in-game diplomacy page barter.toggle = "Ctrl+B" ; Toggle in-game barter/trade page objectives.toggle = "Ctrl+O" ; Toggle in-game objectives page tutorial.toggle = "Ctrl+P" ; Toggle in-game tutorial panel [hotkey.session.savedgames] delete = Delete, Backspace ; Delete the selected saved game asking confirmation noconfirmation = Shift ; Do not ask confirmation when deleting a game [hotkey.session.queueunit] ; > UNIT TRAINING 1 = "Z" ; add first unit type to queue 2 = "X" ; add second unit type to queue 3 = "C" ; add third unit type to queue 4 = "V" ; add fourth unit type to queue 5 = "B" ; add fivth unit type to queue 6 = "N" ; add sixth unit type to queue 7 = "M" ; add seventh unit type to queue 8 = Comma ; add eighth unit type to queue [hotkey.session.timewarp] fastforward = Space ; If timewarp mode enabled, speed up the game rewind = "Shift+Backspace" ; If timewarp mode enabled, go back to earlier point in the game [hotkey.tab] next = "Tab", "Alt+S" ; Show the next tab prev = "Shift+Tab", "Alt+W" ; Show the previous tab [hotkey.text] ; > GUI TEXTBOX HOTKEYS delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor delete.right = "Ctrl+Del" ; Delete word to the right of cursor move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor [gui] cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking) scale = 1.0 ; GUI scaling factor, for improved compatibility with 4K displays [gui.gamesetup] enabletips = true ; Enable/Disable tips during gamesetup (for newcomers) assignplayers = everyone ; Whether to assign joining clients to free playerslots. Possible values: everyone, buddies, disabled. aidifficulty = 3 ; Difficulty level, from 0 (easiest) to 5 (hardest) aibehavior = "random" ; Default behavior of the AI (random, balanced, aggressive or defensive) settingsslide = true ; Enable/Disable settings panel slide [gui.loadingscreen] progressdescription = false ; Whether to display the progress percent or a textual description [gui.session] camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one? timeelapsedcounter = false ; Show the game duration in the top right corner ceasefirecounter = false ; Show the remaining ceasefire time in the top right corner batchtrainingsize = 5 ; Number of units to be trained per batch by default (when pressing the hotkey) scrollbatchratio = 1 ; Number of times you have to scroll to increase/decrease the batchsize by 1 woundedunithotkeythreshold = 33 ; The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number attackrange = true ; Display attack range overlays of selected defensive structures aurasrange = true ; Display aura range overlays of selected units and structures healrange = true ; Display heal range overlays of selected units rankabovestatusbar = true ; Show rank icons above status bars experiencestatusbar = true ; Show an experience status bar above each selected unit respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending) snaptoedges = "disabled" ; Possible values: disabled, enabled. snaptoedgesdistancethreshold = 15 ; On which distance we don't snap to edges disjointcontrolgroups = "true" ; Whether control groups are disjoint sets or entities can be in multiple control groups at the same time. defaultformation = "special/formations/box" ; For walking orders, automatically put units into this formation if they don't have one already. formationwalkonly = "true" ; Formations are disabled when giving gather/attack/... orders. howtoshownames = 0 ; Whether the specific names are show as default, as opposed to the generic names. And whether the secondary names are shown. (0 - show both; specific names primary, 1 - show both; generic names primary, 2 - show only specific names, 3 - show only generic names) [gui.session.minimap] blinkduration = 1.7 ; The blink duration while pinging pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification [gui.session.notifications] attack = true ; Show a chat notification if you are attacked by another player tribute = true ; Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode barter = true ; Show a chat notification to observers when a player bartered resources phase = completed ; Show a chat notification if you or an ally have started, aborted or completed a new phase, and phases of all players in observer mode. Possible values: none, completed, all. [gui.splashscreen] enable = true ; Enable/disable the splashscreen version = 0 ; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch [gui.session.diplomacycolors] self = "21 55 149" ; Color of your units when diplomacy colors are enabled ally = "86 180 31" ; Color of allies when diplomacy colors are enabled neutral = "231 200 5" ; Color of neutral players when diplomacy colors are enabled enemy = "150 20 20" ; Color of enemies when diplomacy colors are enabled [joystick] ; EXPERIMENTAL: joystick/gamepad settings enable = false deadzone = 8192 [chat] timestamp = true ; Show at which time chat messages have been sent [chat.session] extended = true ; Whether to display the chat history [lobby] history = 0 ; Number of past messages to display on join room = "arena25" ; Default MUC room to join server = "lobby.wildfiregames.com" ; Address of lobby server tls = true ; Whether to use TLS encryption when connecting to the server. verify_certificate = false ; Whether to reject connecting to the lobby if the TLS certificate is invalid (TODO: wait for Gloox GnuTLS trust implementation to be fixed) terms_url = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/prelobby/common/terms/"; Allows the user to save the text and print the terms terms_of_service = "0" ; Version (hash) of the Terms of Service that the user has accepted terms_of_use = "0" ; Version (hash) of the Terms of Use that the user has accepted privacy_policy = "0" ; Version (hash) of the Privacy Policy that the user has accepted xpartamupp = "wfgbot25" ; Name of the server-side XMPP-account that manage games echelon = "echelon25" ; Name of the server-side XMPP-account that manages ratings buddies = "," ; Comma separated list of playernames that the current user has marked as buddies rememberpassword = true ; Whether to store the encrypted password in the user config [lobby.columns] gamerating = false ; Show the average rating of the participating players in a column of the gamelist [lobby.stun] enabled = true ; The STUN protocol allows hosting games without configuring the firewall and router. ; If STUN is disabled, the game relies on direct connection, UPnP and port forwarding. server = "lobby.wildfiregames.com" ; Address of the STUN server. port = 3478 ; Port of the STUN server. delay = 200 ; Duration in milliseconds that is waited between STUN messages. ; Smaller numbers speed up joins but also become less stable. [mod] enabledmods = "mod public" [modio] public_key = "RWRcbM/EwV7bucTiQVCcRBhCkYkXmJEO7s4ktyufkB+gW/NxHhOZ38xh" ; Public key corresponding to the private key valid mods are signed with disclaimer = "0" ; Version (hash) of the Disclaimer that the user has accepted [modio.v1] baseurl = "https://api.mod.io/v1" api_key = "23df258a71711ea6e4b50893acc1ba55" name_id = "0ad" [network] duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled. observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached observermaxlag = 10 ; Make clients wait for observers if they lag more than X turns behind. -1 means "never wait for observers". autocatchup = true ; Auto-accelerate the sim rate if lagging behind (as an observer). [overlay] fps = "false" ; Show frames per second in top right corner realtime = "false" ; Show current system time in top right corner netwarnings = "true" ; Show warnings if the network connection is bad [profiler2] autoenable = false ; Enable HTTP server output at startup (default off for security/performance) gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available [rlinterface] address = "127.0.0.1:6000" [sound] mastergain = 0.9 musicgain = 0.2 ambientgain = 0.6 actiongain = 0.7 uigain = 0.7 mindistance = 1 maxdistance = 350 maxstereoangle = 0.62 ; About PI/5 radians [sound.notify] nick = true ; Play a sound when someone mentions your name in the lobby or game gamesetup.join = false ; Play a sound when a new client joins the game setup [tinygettext] debug = false ; Print error messages each time a translation for an English string is not found. [userreport] ; Opt-in online user reporting system url_upload = "https://feedback.wildfiregames.com/report/upload/v1/" ; URL where UserReports are uploaded to url_publication = "https://feedback.wildfiregames.com/" ; URL where UserReports were analyzed and published url_terms = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/userreport/Terms_and_Conditions.txt"; Allows the user to save the text and print the terms terms = "0" ; Version (hash) of the UserReporter Terms that the user has accepted [view] ; Camera control settings scroll.speed = 120.0 scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed rotate.x.speed = 1.2 rotate.x.min = 28.0 rotate.x.max = 60.0 rotate.x.default = 35.0 rotate.y.speed = 2.0 rotate.y.speed.wheel = 0.45 rotate.y.default = 0.0 rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed drag.speed = 0.5 zoom.speed = 256.0 zoom.speed.wheel = 32.0 zoom.min = 50.0 zoom.max = 200.0 zoom.default = 120.0 zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed pos.smoothness = 0.1 zoom.smoothness = 0.4 rotate.x.smoothness = 0.5 rotate.y.smoothness = 0.3 near = 2.0 ; Near plane distance far = 4096.0 ; Far plane distance fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide height.smoothness = 0.5 height.min = 16 Index: ps/trunk/source/lib/sysdep/os/win/wutil.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wutil.cpp (revision 25691) +++ ps/trunk/source/lib/sysdep/os/win/wutil.cpp (revision 25692) @@ -1,463 +1,489 @@ /* Copyright (C) 2021 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. */ /* * various Windows-specific utilities */ #include "precompiled.h" #include "lib/sysdep/os/win/wutil.h" #include #include // __argc #include "lib/file/file.h" #include "lib/posix/posix.h" #include "lib/sysdep/sysdep.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wdbg.h" // wdbg_assert #include "lib/sysdep/os/win/winit.h" #include // SHGetFolderPath +#include + WINIT_REGISTER_EARLY_INIT(wutil_Init); WINIT_REGISTER_LATE_SHUTDOWN(wutil_Shutdown); // Defined in ps/Pyrogenesis.h extern const char* main_window_name; //----------------------------------------------------------------------------- // safe allocator // may be used independently of libc malloc // (in particular, before _cinit and while calling static dtors). // used by wpthread critical section code. void* wutil_Allocate(size_t size) { const DWORD flags = HEAP_ZERO_MEMORY; return HeapAlloc(GetProcessHeap(), flags, size); } void wutil_Free(void* p) { const DWORD flags = 0; HeapFree(GetProcessHeap(), flags, p); } //----------------------------------------------------------------------------- // locks // several init functions are before called before _cinit. // POSIX static mutex init may not have been done by then, // so we need our own lightweight functions. static CRITICAL_SECTION cs[NUM_CS]; static bool cs_valid; void wutil_Lock(WinLockId id) { if(!cs_valid) return; EnterCriticalSection(&cs[id]); } void wutil_Unlock(WinLockId id) { if(!cs_valid) return; LeaveCriticalSection(&cs[id]); } bool wutil_IsLocked(WinLockId id) { if(!cs_valid) return false; const BOOL successfullyEntered = TryEnterCriticalSection(&cs[id]); if(!successfullyEntered) return true; // still locked LeaveCriticalSection(&cs[id]); return false; // probably not locked } static void InitLocks() { for(int i = 0; i < NUM_CS; i++) InitializeCriticalSection(&cs[i]); cs_valid = true; } static void ShutdownLocks() { cs_valid = false; for(int i = 0; i < NUM_CS; i++) DeleteCriticalSection(&cs[i]); memset(cs, 0, sizeof(cs)); } //----------------------------------------------------------------------------- // error codes // only call after a Win32 function indicates failure. Status StatusFromWin() { switch(GetLastError()) { case ERROR_BUSY: case WAIT_TIMEOUT: return ERR::AGAIN; case ERROR_OPERATION_ABORTED: return ERR::ABORTED; case ERROR_INVALID_HANDLE: return ERR::INVALID_HANDLE; case ERROR_INSUFFICIENT_BUFFER: return ERR::INVALID_SIZE; case ERROR_INVALID_PARAMETER: case ERROR_BAD_ARGUMENTS: return ERR::INVALID_PARAM; case ERROR_OUTOFMEMORY: case ERROR_NOT_ENOUGH_MEMORY: return ERR::NO_MEM; case ERROR_NOT_SUPPORTED: case ERROR_CALL_NOT_IMPLEMENTED: case ERROR_PROC_NOT_FOUND: return ERR::NOT_SUPPORTED; case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return ERR::FILE_NOT_FOUND; case ERROR_ACCESS_DENIED: return ERR::FILE_ACCESS; default: return ERR::FAIL; } } //----------------------------------------------------------------------------- // directories // (NB: wutil_Init is called before static ctors => use placement new) static OsPath* systemPath; static OsPath* executablePath; static OsPath* localAppdataPath; static OsPath* roamingAppdataPath; static OsPath* personalPath; const OsPath& wutil_SystemPath() { return *systemPath; } const OsPath& wutil_ExecutablePath() { return *executablePath; } const OsPath& wutil_LocalAppdataPath() { return *localAppdataPath; } const OsPath& wutil_RoamingAppdataPath() { return *roamingAppdataPath; } const OsPath& wutil_PersonalPath() { return *personalPath; } // Helper to avoid duplicating this setup static OsPath* GetFolderPath(int csidl) { HWND hwnd = 0; // ignored unless a dial-up connection is needed to access the folder HANDLE token = 0; wchar_t path[MAX_PATH]; // mandated by SHGetFolderPathW const HRESULT ret = SHGetFolderPathW(hwnd, csidl, token, 0, path); if (!SUCCEEDED(ret)) { debug_printf("SHGetFolderPathW failed with HRESULT = 0x%08lx for csidl = 0x%04x\n", ret, csidl); debug_warn("SHGetFolderPathW failed (see debug output)"); } if(GetLastError() == ERROR_NO_TOKEN) // avoid polluting last error SetLastError(0); return new(wutil_Allocate(sizeof(OsPath))) OsPath(path); } static void GetDirectories() { WinScopedPreserveLastError s; // system directory { const UINT length = GetSystemDirectoryW(0, 0); ENSURE(length != 0); std::wstring path(length, '\0'); const UINT charsWritten = GetSystemDirectoryW(&path[0], length); ENSURE(charsWritten == length-1); systemPath = new(wutil_Allocate(sizeof(OsPath))) OsPath(path); } // executable's directory executablePath = new(wutil_Allocate(sizeof(OsPath))) OsPath(sys_ExecutablePathname().Parent()); // roaming application data roamingAppdataPath = GetFolderPath(CSIDL_APPDATA); // local application data localAppdataPath = GetFolderPath(CSIDL_LOCAL_APPDATA); // my documents personalPath = GetFolderPath(CSIDL_PERSONAL); } static void FreeDirectories() { systemPath->~OsPath(); wutil_Free(systemPath); executablePath->~OsPath(); wutil_Free(executablePath); localAppdataPath->~OsPath(); wutil_Free(localAppdataPath); roamingAppdataPath->~OsPath(); wutil_Free(roamingAppdataPath); personalPath->~OsPath(); wutil_Free(personalPath); } //----------------------------------------------------------------------------- // memory static void EnableLowFragmentationHeap() { if(IsDebuggerPresent()) { // and the debug heap isn't explicitly disabled, char* var = getenv("_NO_DEBUG_HEAP"); if(!var || var[0] != '1') return; // we can't enable the LFH } #if WINVER >= 0x0501 WUTIL_FUNC(pHeapSetInformation, BOOL, (HANDLE, HEAP_INFORMATION_CLASS, void*, size_t)); WUTIL_IMPORT_KERNEL32(HeapSetInformation, pHeapSetInformation); if(pHeapSetInformation) { ULONG flags = 2; // enable LFH pHeapSetInformation(GetProcessHeap(), HeapCompatibilityInformation, &flags, sizeof(flags)); } #endif // #if WINVER >= 0x0501 } //----------------------------------------------------------------------------- // Wow64 // Wow64 'helpfully' redirects all 32-bit apps' accesses of // %windir%\\system32\\drivers to %windir%\\system32\\drivers\\SysWOW64. // that's bad, because the actual drivers are not in the subdirectory. to // work around this, provide for temporarily disabling redirection. static WUTIL_FUNC(pIsWow64Process, BOOL, (HANDLE, PBOOL)); static WUTIL_FUNC(pWow64DisableWow64FsRedirection, BOOL, (PVOID*)); static WUTIL_FUNC(pWow64RevertWow64FsRedirection, BOOL, (PVOID)); static bool isWow64; static void ImportWow64Functions() { WUTIL_IMPORT_KERNEL32(IsWow64Process, pIsWow64Process); WUTIL_IMPORT_KERNEL32(Wow64DisableWow64FsRedirection, pWow64DisableWow64FsRedirection); WUTIL_IMPORT_KERNEL32(Wow64RevertWow64FsRedirection, pWow64RevertWow64FsRedirection); } static void DetectWow64() { // function not found => running on 32-bit Windows if(!pIsWow64Process) { isWow64 = false; return; } BOOL isWow64Process = FALSE; const BOOL ok = pIsWow64Process(GetCurrentProcess(), &isWow64Process); WARN_IF_FALSE(ok); isWow64 = (isWow64Process == TRUE); } bool wutil_IsWow64() { return isWow64; } WinScopedDisableWow64Redirection::WinScopedDisableWow64Redirection() { // note: don't just check if the function pointers are valid. 32-bit // Vista includes them but isn't running Wow64, so calling the functions // would fail. since we have to check if actually on Wow64, there's no // more need to verify the pointers (their existence is implied). if(!wutil_IsWow64()) return; const BOOL ok = pWow64DisableWow64FsRedirection(&m_wasRedirectionEnabled); WARN_IF_FALSE(ok); } WinScopedDisableWow64Redirection::~WinScopedDisableWow64Redirection() { if(!wutil_IsWow64()) return; const BOOL ok = pWow64RevertWow64FsRedirection(m_wasRedirectionEnabled); WARN_IF_FALSE(ok); } //----------------------------------------------------------------------------- Status wutil_SetPrivilege(const wchar_t* privilege, bool enable) { WinScopedPreserveLastError s; HANDLE hToken; if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken)) return ERR::_1; TOKEN_PRIVILEGES tp; if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid)) return ERR::_2; tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = enable? SE_PRIVILEGE_ENABLED : 0; SetLastError(0); const BOOL ok = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, 0, 0); if(!ok || GetLastError() != 0) return ERR::_3; WARN_IF_FALSE(CloseHandle(hToken)); return INFO::OK; } //----------------------------------------------------------------------------- // module handle #ifndef LIB_STATIC_LINK #include "lib/sysdep/os/win/wdll_main.h" HMODULE wutil_LibModuleHandle() { HMODULE hModule; const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; const BOOL ok = GetModuleHandleEx(flags, (LPCWSTR)&wutil_LibModuleHandle, &hModule); // (avoid ENSURE etc. because we're called from debug_DisplayError) wdbg_assert(ok); return hModule; } #else HMODULE wutil_LibModuleHandle() { return GetModuleHandle(0); } #endif //----------------------------------------------------------------------------- // find main window // this is required by the error dialog and clipboard code. // note that calling from wutil_Init won't work, because the app will not // have created its window by then. static HWND hAppWindow; static BOOL CALLBACK FindAppWindowByPid(HWND hWnd, LPARAM UNUSED(lParam)) { DWORD pid; DWORD tid = GetWindowThreadProcessId(hWnd, &pid); UNUSED2(tid); if(pid == GetCurrentProcessId()) { char windowName[100]; GetWindowTextA(hWnd, windowName, 99); if (strcmp(windowName, main_window_name) == 0) hAppWindow = hWnd; } return TRUE; // keep calling } HWND wutil_AppWindow() { if(!hAppWindow) { WARN_IF_FALSE(EnumWindows(FindAppWindowByPid, 0)); // (hAppWindow may still be 0 if we haven't created a window yet) } return hAppWindow; } +void wutil_EnableHiDPIOnWindows() +{ + // We build with VS using XP toolkit which doesn't support DPI awareness. + // It was introduced in 8.1. + // https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness + typedef enum PROCESS_DPI_AWARENESS { + PROCESS_DPI_UNAWARE, + PROCESS_SYSTEM_DPI_AWARE, + PROCESS_PER_MONITOR_DPI_AWARE + }; + + // https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness + using SetProcessDpiAwarenessFunc = HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS); + void* shcoreDLL = SDL_LoadObject("SHCORE.DLL"); + if (!shcoreDLL) + return; + + SetProcessDpiAwarenessFunc SetProcessDpiAwareness = + reinterpret_cast(SDL_LoadFunction(shcoreDLL, "SetProcessDpiAwareness")); + if (SetProcessDpiAwareness) + SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + + SDL_UnloadObject(shcoreDLL); +} //----------------------------------------------------------------------------- static Status wutil_Init() { InitLocks(); EnableLowFragmentationHeap(); GetDirectories(); ImportWow64Functions(); DetectWow64(); return INFO::OK; } static Status wutil_Shutdown() { ShutdownLocks(); FreeDirectories(); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/os/win/wutil.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wutil.h (revision 25691) +++ ps/trunk/source/lib/sysdep/os/win/wutil.h (revision 25692) @@ -1,204 +1,206 @@ /* Copyright (C) 2021 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. */ /* * various Windows-specific utilities */ #ifndef INCLUDED_WUTIL #define INCLUDED_WUTIL #ifdef _MSC_VER # pragma warning(disable:4091) // hides previous local declaration #endif #include "lib/os_path.h" #include "lib/sysdep/os/win/win.h" template bool wutil_IsValidHandle(H h) { return h != 0 && h != INVALID_HANDLE_VALUE; } //----------------------------------------------------------------------------- // dynamic linking // define a function pointer (optionally prepend 'static') #define WUTIL_FUNC(varName, ret, params)\ ret (WINAPI* varName) params // rationale: // - splitting up WUTIL_FUNC and WUTIL_IMPORT is a bit verbose in // the common case of a local function pointer definition, // but allows one-time initialization of static variables. // - differentiating between procName and varName allows searching // for the actual definition of the function pointer in the code. // - a cast would require passing in ret/params. // - writing a type-punned pointer breaks strict-aliasing rules. #define WUTIL_IMPORT(hModule, procName, varName)\ STMT(\ const FARPROC f = GetProcAddress(hModule, #procName);\ memcpy(&varName, &f, sizeof(FARPROC));\ ) // note: Kernel32 is guaranteed to be loaded, so we don't // need to LoadLibrary and FreeLibrary. #define WUTIL_IMPORT_KERNEL32(procName, varName)\ WUTIL_IMPORT(GetModuleHandleW(L"kernel32.dll"), procName, varName) // note: ntdll is guaranteed to be loaded, so we don't // need to LoadLibrary and FreeLibrary. #define WUTIL_IMPORT_NTDLL(procName, varName)\ WUTIL_IMPORT(GetModuleHandleW(L"ntdll.dll"), procName, varName) //----------------------------------------------------------------------------- // safe allocator extern void* wutil_Allocate(size_t size); extern void wutil_Free(void* p); //----------------------------------------------------------------------------- // locks // critical sections used by win-specific code enum WinLockId { WDBG_SYM_CS, // protects (non-reentrant) dbghelp.dll WDIR_WATCH_CS, NUM_CS }; extern void wutil_Lock(WinLockId id); extern void wutil_Unlock(WinLockId id); // used in a desperate attempt to avoid deadlock in wseh. extern bool wutil_IsLocked(WinLockId id); class WinScopedLock { public: WinScopedLock(WinLockId id) : m_id(id) { wutil_Lock(m_id); } ~WinScopedLock() { wutil_Unlock(m_id); } private: WinLockId m_id; }; //----------------------------------------------------------------------------- // errors /** * some WinAPI functions SetLastError(0) on success, which is bad because * it can hide previous errors. this class takes care of restoring the * previous value. **/ class WinScopedPreserveLastError { public: WinScopedPreserveLastError() : m_lastError(GetLastError()) { SetLastError(0); } ~WinScopedPreserveLastError() { if(m_lastError != 0 && GetLastError() == 0) SetLastError(m_lastError); } private: DWORD m_lastError; }; /** * @return the Status equivalent of GetLastError(), or ERR::FAIL if * there's no equivalent. * SetLastError(0) should be called before the Windows function to * make sure no stale errors are returned. **/ extern Status StatusFromWin(); //----------------------------------------------------------------------------- // directories extern const OsPath& wutil_SystemPath(); extern const OsPath& wutil_ExecutablePath(); extern const OsPath& wutil_LocalAppdataPath(); extern const OsPath& wutil_RoamingAppdataPath(); extern const OsPath& wutil_PersonalPath(); //----------------------------------------------------------------------------- // Wow64 extern bool wutil_IsWow64(); class WinScopedDisableWow64Redirection { public: WinScopedDisableWow64Redirection(); ~WinScopedDisableWow64Redirection(); private: void* m_wasRedirectionEnabled; }; //----------------------------------------------------------------------------- LIB_API Status wutil_SetPrivilege(const wchar_t* privilege, bool enable); /** * @return module handle of lib code (that of the main EXE if * linked statically, otherwise the DLL). * this is necessary for the error dialog. **/ extern HMODULE wutil_LibModuleHandle(); /** * @return handle to the first window owned by the current process, or * 0 if none exist (e.g. it hasn't yet created one). * * enumerates all top-level windows and stops if PID matches. * once this function returns a non-NULL handle, it will always * return that cached value. **/ extern HWND wutil_AppWindow(); +extern void wutil_EnableHiDPIOnWindows(); + #endif // #ifndef INCLUDED_WUTIL Index: ps/trunk/source/ps/VideoMode.cpp =================================================================== --- ps/trunk/source/ps/VideoMode.cpp (revision 25691) +++ ps/trunk/source/ps/VideoMode.cpp (revision 25692) @@ -1,540 +1,556 @@ /* Copyright (C) 2021 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 "VideoMode.h" -#include "graphics/Camera.h" #include "graphics/GameView.h" #include "gui/GUIManager.h" #include "lib/config2.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStr.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Pyrogenesis.h" #include "renderer/Renderer.h" -static int DEFAULT_WINDOW_W = 1024; -static int DEFAULT_WINDOW_H = 768; +namespace +{ + +int DEFAULT_WINDOW_W = 1024; +int DEFAULT_WINDOW_H = 768; -static int DEFAULT_FULLSCREEN_W = 1024; -static int DEFAULT_FULLSCREEN_H = 768; +int DEFAULT_FULLSCREEN_W = 1024; +int DEFAULT_FULLSCREEN_H = 768; + +} // anonymous namespace + +#if OS_WIN +// After a proper HiDPI integration we should switch to manifest until +// SDL has an implemented HiDPI on Windows. +extern void wutil_EnableHiDPIOnWindows(); +#endif CVideoMode g_VideoMode; CVideoMode::CVideoMode() : m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0) { } void CVideoMode::ReadConfig() { bool windowed = !m_ConfigFullscreen; CFG_GET_VAL("windowed", windowed); m_ConfigFullscreen = !windowed; CFG_GET_VAL("xres", m_ConfigW); CFG_GET_VAL("yres", m_ConfigH); CFG_GET_VAL("bpp", m_ConfigBPP); CFG_GET_VAL("display", m_ConfigDisplay); - + CFG_GET_VAL("hidpi", m_ConfigEnableHiDPI); CFG_GET_VAL("vsync", m_ConfigVSync); } bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen) { Uint32 flags = 0; if (fullscreen) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; if (!m_Window) { +#if OS_WIN + if (m_ConfigEnableHiDPI) + wutil_EnableHiDPIOnWindows(); +#endif // Note: these flags only take affect in SDL_CreateWindow flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE; + if (m_ConfigEnableHiDPI) + flags |= SDL_WINDOW_ALLOW_HIGHDPI; m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay); m_Window = SDL_CreateWindow(main_window_name, m_WindowedX, m_WindowedY, w, h, flags); if (!m_Window) { // If fullscreen fails, try windowed mode if (fullscreen) { LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution " "%dx%d:%d (\"%hs\"), falling back to windowed mode", w, h, bpp, SDL_GetError()); // Using default size for the window for now, as the attempted setting // could be as large, or larger than the screen size. return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false); } else { LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")", w, h, bpp, fullscreen ? 1 : 0, SDL_GetError()); return false; } } if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0) { LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")", w, h, bpp, fullscreen ? 1 : 0, SDL_GetError()); return false; } SDL_GLContext context = SDL_GL_CreateContext(m_Window); if (!context) { LOGERROR("SetVideoMode failed in SDL_GL_CreateContext: %dx%d:%d %d (\"%s\")", w, h, bpp, fullscreen ? 1 : 0, SDL_GetError()); return false; } } else { if (m_IsFullscreen != fullscreen) { if (!fullscreen) { // For some reason, when switching from fullscreen to windowed mode, // we have to set the window size and position before and after switching SDL_SetWindowSize(m_Window, w, h); SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY); } if (SDL_SetWindowFullscreen(m_Window, flags) < 0) { LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")", w, h, bpp, fullscreen ? 1 : 0, SDL_GetError()); return false; } } if (!fullscreen) { SDL_SetWindowSize(m_Window, w, h); SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY); } } // Grab the current video settings SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH); m_CurrentBPP = bpp; if (fullscreen) SDL_SetWindowGrab(m_Window, SDL_TRUE); else SDL_SetWindowGrab(m_Window, SDL_FALSE); m_IsFullscreen = fullscreen; g_xres = m_CurrentW; g_yres = m_CurrentH; return true; } bool CVideoMode::InitSDL() { ENSURE(!m_IsInitialised); ReadConfig(); // preferred video mode = current desktop settings // (command line params may override these) // TODO: handle multi-screen and HiDPI properly. SDL_DisplayMode mode; if (SDL_GetDesktopDisplayMode(0, &mode) == 0) { m_PreferredW = mode.w; m_PreferredH = mode.h; m_PreferredBPP = SDL_BITSPERPIXEL(mode.format); m_PreferredFreq = mode.refresh_rate; } int w = m_ConfigW; int h = m_ConfigH; if (m_ConfigFullscreen) { // If fullscreen and no explicit size set, default to the desktop resolution if (w == 0 || h == 0) { w = m_PreferredW; h = m_PreferredH; } } // If no size determined, default to something sensible if (w == 0 || h == 0) { w = DEFAULT_WINDOW_W; h = DEFAULT_WINDOW_H; } if (!m_ConfigFullscreen) { // Limit the window to the screen size (if known) if (m_PreferredW) w = std::min(w, m_PreferredW); if (m_PreferredH) h = std::min(h, m_PreferredH); } int bpp = GetBestBPP(); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); #if CONFIG2_GLES // Require GLES 2.0 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #endif bool forceGLVersion = false; CFG_GET_VAL("forceglversion", forceGLVersion); if (forceGLVersion) { CStr forceGLProfile = "compatibility"; int forceGLMajorVersion = 3; int forceGLMinorVersion = 0; CFG_GET_VAL("forceglprofile", forceGLProfile); CFG_GET_VAL("forceglmajorversion", forceGLMajorVersion); CFG_GET_VAL("forceglminorversion", forceGLMinorVersion); int profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; if (forceGLProfile == "es") profile = SDL_GL_CONTEXT_PROFILE_ES; else if (forceGLProfile == "core") profile = SDL_GL_CONTEXT_PROFILE_CORE; else if (forceGLProfile != "compatibility") LOGWARNING("Unknown force GL profile '%s', compatibility profile is used", forceGLProfile.c_str()); if (forceGLMajorVersion < 1 || forceGLMinorVersion < 0) { LOGERROR("Unsupported force GL version: %d.%d", forceGLMajorVersion, forceGLMinorVersion); } else { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, forceGLMajorVersion); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, forceGLMinorVersion); } } if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen)) { // Fall back to a smaller depth buffer // (The rendering may be ugly but this helps when running in VMware) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen)) return false; } SDL_GL_SetSwapInterval(m_ConfigVSync ? 1 : 0); // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13). // The driver appears to register its own atexit hook on context creation. // If this atexit hook is called before SDL_Quit destroys the OpenGL context, // some kind of double-free problem causes a crash and lockup in the driver. // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem // by destroying the context *before* the driver's atexit hook is called. // (Note that atexit hooks are guaranteed to be called in reverse order of their registration.) atexit(SDL_Quit); // End work around. ogl_Init(); // required after each mode change // (TODO: does that mean we need to call this when toggling fullscreen later?) m_IsInitialised = true; if (!m_ConfigFullscreen) { m_WindowedW = w; m_WindowedH = h; } SetWindowIcon(); return true; } bool CVideoMode::InitNonSDL() { ENSURE(!m_IsInitialised); ReadConfig(); m_IsInitialised = true; return true; } void CVideoMode::Shutdown() { ENSURE(m_IsInitialised); m_IsFullscreen = false; m_IsInitialised = false; if (m_Window) { SDL_DestroyWindow(m_Window); m_Window = NULL; } } bool CVideoMode::ResizeWindow(int w, int h) { ENSURE(m_IsInitialised); // Ignore if not windowed if (m_IsFullscreen) return true; // Ignore if the size hasn't changed if (w == m_WindowedW && h == m_WindowedH) return true; int bpp = GetBestBPP(); if (!SetVideoMode(w, h, bpp, false)) return false; m_WindowedW = w; m_WindowedH = h; UpdateRenderer(w, h); return true; } bool CVideoMode::SetFullscreen(bool fullscreen) { // This might get called before initialisation by psDisplayError; // if so then silently fail if (!m_IsInitialised) return false; // Check whether this is actually a change if (fullscreen == m_IsFullscreen) return true; if (!m_IsFullscreen) { // Windowed -> fullscreen: int w = 0, h = 0; // If a fullscreen size was configured, use that; else use the desktop size; else use a default if (m_ConfigFullscreen) { w = m_ConfigW; h = m_ConfigH; } if (w == 0 || h == 0) { w = m_PreferredW; h = m_PreferredH; } if (w == 0 || h == 0) { w = DEFAULT_FULLSCREEN_W; h = DEFAULT_FULLSCREEN_H; } int bpp = GetBestBPP(); if (!SetVideoMode(w, h, bpp, fullscreen)) return false; UpdateRenderer(m_CurrentW, m_CurrentH); return true; } else { // Fullscreen -> windowed: // Go back to whatever the previous window size was int w = m_WindowedW, h = m_WindowedH; int bpp = GetBestBPP(); if (!SetVideoMode(w, h, bpp, fullscreen)) return false; UpdateRenderer(w, h); return true; } } bool CVideoMode::ToggleFullscreen() { return SetFullscreen(!m_IsFullscreen); } bool CVideoMode::IsInFullscreen() const { return m_IsFullscreen; } void CVideoMode::UpdatePosition(int x, int y) { if (!m_IsFullscreen) { m_WindowedX = x; m_WindowedY = y; } } void CVideoMode::UpdateRenderer(int w, int h) { if (w < 2) w = 2; // avoid GL errors caused by invalid sizes if (h < 2) h = 2; g_xres = w; g_yres = h; SViewPort vp = { 0, 0, w, h }; if (CRenderer::IsInitialised()) { g_Renderer.SetViewport(vp); g_Renderer.Resize(w, h); } if (g_GUI) g_GUI->UpdateResolution(); if (g_Console) g_Console->UpdateScreenSize(w, h); if (g_Game) g_Game->GetView()->SetViewport(vp); } int CVideoMode::GetBestBPP() { if (m_ConfigBPP) return m_ConfigBPP; if (m_PreferredBPP) return m_PreferredBPP; return 32; } int CVideoMode::GetXRes() const { ENSURE(m_IsInitialised); return m_CurrentW; } int CVideoMode::GetYRes() const { ENSURE(m_IsInitialised); return m_CurrentH; } int CVideoMode::GetBPP() const { ENSURE(m_IsInitialised); return m_CurrentBPP; } bool CVideoMode::IsVSyncEnabled() const { ENSURE(m_IsInitialised); return m_ConfigVSync; } int CVideoMode::GetDesktopXRes() const { ENSURE(m_IsInitialised); return m_PreferredW; } int CVideoMode::GetDesktopYRes() const { ENSURE(m_IsInitialised); return m_PreferredH; } int CVideoMode::GetDesktopBPP() const { ENSURE(m_IsInitialised); return m_PreferredBPP; } int CVideoMode::GetDesktopFreq() const { ENSURE(m_IsInitialised); return m_PreferredFreq; } SDL_Window* CVideoMode::GetWindow() { ENSURE(m_IsInitialised); return m_Window; } void CVideoMode::SetWindowIcon() { // The window icon should be kept outside of art/textures/, or else it will be converted // to DDS by the archive builder and will become unusable here. Using DDS makes BGRA // conversion needlessly complicated. std::shared_ptr iconFile; size_t iconFileSize; if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK) { LOGWARNING("Window icon not found."); return; } Tex iconTexture; if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK) return; // Convert to required BGRA format. const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT; if (iconTexture.transform_to(iconFlags) != INFO::OK) return; void* bgra_img = iconTexture.get_data(); if (!bgra_img) return; SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img, iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if (!iconSurface) return; SDL_SetWindowIcon(m_Window, iconSurface); SDL_FreeSurface(iconSurface); } Index: ps/trunk/source/ps/VideoMode.h =================================================================== --- ps/trunk/source/ps/VideoMode.h (revision 25691) +++ ps/trunk/source/ps/VideoMode.h (revision 25692) @@ -1,141 +1,142 @@ /* Copyright (C) 2021 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 . */ #ifndef INCLUDED_VIDEOMODE #define INCLUDED_VIDEOMODE typedef struct SDL_Window SDL_Window; class CVideoMode { public: CVideoMode(); /** * Initialise the video mode, for use in an SDL-using application. */ bool InitSDL(); /** * Initialise parts of the video mode, for use in Atlas (which uses * wxWidgets instead of SDL for GL). */ bool InitNonSDL(); /** * Shut down after InitSDL/InitNonSDL, so that they can be used again. */ void Shutdown(); /** * Resize the SDL window and associated graphics stuff to the new size. */ bool ResizeWindow(int w, int h); /** * Switch to fullscreen or windowed mode. */ bool SetFullscreen(bool fullscreen); /** * Returns true if window runs in fullscreen mode. */ bool IsInFullscreen() const; /** * Switch between fullscreen and windowed mode. */ bool ToggleFullscreen(); /** * Update window position, to restore later if necessary (SDL2 only). */ void UpdatePosition(int x, int y); /** * Update the graphics code to start drawing to the new size. * This should be called after the GL context has been resized. * This can also be used when the GL context is managed externally, not via SDL. */ static void UpdateRenderer(int w, int h); int GetXRes() const; int GetYRes() const; int GetBPP() const; bool IsVSyncEnabled() const; int GetDesktopXRes() const; int GetDesktopYRes() const; int GetDesktopBPP() const; int GetDesktopFreq() const; SDL_Window* GetWindow(); void SetWindowIcon(); private: void ReadConfig(); int GetBestBPP(); bool SetVideoMode(int w, int h, int bpp, bool fullscreen); /** * Remember whether Init has been called. (This isn't used for anything * important, just for verifying that the callers call our methods in * the right order.) */ bool m_IsInitialised = false; SDL_Window* m_Window = nullptr; // Initial desktop settings. // Frequency is in Hz, and BPP means bits per pixels (not bytes per pixels). int m_PreferredW = 0; int m_PreferredH = 0; int m_PreferredBPP = 0; int m_PreferredFreq = 0; // Config file settings (0 if unspecified) int m_ConfigW = 0; int m_ConfigH = 0; int m_ConfigBPP = 0; int m_ConfigDisplay = 0; + bool m_ConfigEnableHiDPI = false; bool m_ConfigVSync = false; // (m_ConfigFullscreen defaults to false, so users don't get stuck if // e.g. half the filesystem is missing and the config files aren't loaded). bool m_ConfigFullscreen = false; // If we're fullscreen, size/position of window when we were last windowed (or the default window // size/position if we started fullscreen), to support switching back to the old window size/position int m_WindowedW; int m_WindowedH; int m_WindowedX; int m_WindowedY; // Whether we're currently being displayed fullscreen bool m_IsFullscreen = false; // The last mode selected int m_CurrentW; int m_CurrentH; int m_CurrentBPP; }; extern CVideoMode g_VideoMode; #endif // INCLUDED_VIDEOMODE