diff --git a/CMake/CMakeMacros.cmake b/CMake/CMakeMacros.cmake index 82428a840f..971ac42060 100644 --- a/CMake/CMakeMacros.cmake +++ b/CMake/CMakeMacros.cmake @@ -27,7 +27,13 @@ function(sm_add_compile_definition target def) endfunction() function(sm_add_compile_flag target flag) - sm_append_simple_target_property(${target} COMPILE_FLAGS ${flag}) + get_target_property(current_property ${target} COMPILE_FLAGS) + if (current_property) + set(current_property "${current_property} ${flag}") + set_target_properties(${target} PROPERTIES COMPILE_FLAGS "${current_property}") + else() + set_target_properties(${target} PROPERTIES COMPILE_FLAGS ${flag}) + endif() endfunction() function(sm_add_link_flag target flag) diff --git a/CMake/DefineOptions.cmake b/CMake/DefineOptions.cmake index 0a3240587a..003bb27406 100644 --- a/CMake/DefineOptions.cmake +++ b/CMake/DefineOptions.cmake @@ -68,4 +68,5 @@ elseif(LINUX) option(WITH_GTK2 "Build with GTK2 Support." ON) option(WITH_PARALLEL_PORT "Build with Parallel Lights I/O Support." OFF) option(WITH_CRASH_HANDLER "Build with Crash Handler Support." ON) + option(WITH_XINERAMA "Build using libXinerama to query for monitor numbers (if available)." ON) endif() diff --git a/CMake/Modules/FindXinerama.cmake b/CMake/Modules/FindXinerama.cmake new file mode 100644 index 0000000000..7574dc3e93 --- /dev/null +++ b/CMake/Modules/FindXinerama.cmake @@ -0,0 +1,52 @@ +# This is borrowed from FreeRDP. +# - Find XINERAMA +# Find the XINERAMA libraries +# +# This module defines the following variables: +# XINERAMA_FOUND - true if XINERAMA_INCLUDE_DIR & XINERAMA_LIBRARY are found +# XINERAMA_LIBRARIES - Set when XINERAMA_LIBRARY is found +# XINERAMA_INCLUDE_DIRS - Set when XINERAMA_INCLUDE_DIR is found +# +# XINERAMA_INCLUDE_DIR - where to find Xinerama.h, etc. +# XINERAMA_LIBRARY - the XINERAMA library +# + +#============================================================================= +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +find_path(XINERAMA_INCLUDE_DIR NAMES X11/extensions/Xinerama.h + PATHS /opt/X11/include + PATH_SUFFIXES X11/extensions + DOC "The Xinerama include directory" +) + +find_library(XINERAMA_LIBRARY NAMES Xinerama + PATHS /opt/X11/lib + DOC "The Xinerama library" +) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Xinerama DEFAULT_MSG XINERAMA_LIBRARY XINERAMA_INCLUDE_DIR) + +if(XINERAMA_FOUND) + set( XINERAMA_LIBRARIES ${XINERAMA_LIBRARY} ) + set( XINERAMA_INCLUDE_DIRS ${XINERAMA_INCLUDE_DIR} ) +endif() + +mark_as_advanced(XINERAMA_INCLUDE_DIR XINERAMA_LIBRARY) + diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index a45489da10..739ae7e146 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -3,6 +3,11 @@ ________________________________________________________________________________ The StepMania 5.1 Changelog covers all post-5.0.12 changes. ________________________________________________________________________________ +2017/06/18 +---------- +* [RageDisplay, LLW_X11] Backport fullscreen borderless window, per-monitor + fullscreen options to X11 backend + 2017/06/06 ---------- * [General] Backport custom song support. [kyzentun] diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 8d2c1ece3a..b1948b7b49 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -744,6 +744,23 @@ + + + + + + + + + + + + + + + + + @@ -1480,8 +1497,11 @@ + + + @@ -2479,6 +2499,15 @@ + + + + + + + + + @@ -2566,6 +2595,11 @@ + + + + + @@ -2800,6 +2834,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index dbb262d4d3..c7bd3d3849 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -21,7 +21,7 @@ save yourself some time, copy this for undocumented things: - + --> @@ -451,7 +451,7 @@ save yourself some time, copy this for undocumented things: Returns a formatted percent with the specified numerator and denominator. - [00 init.lua] + [00 init.lua] [03 ThemePrefs.lua] Prints a table's contents to the log. @@ -568,7 +568,7 @@ save yourself some time, copy this for undocumented things: [03 UserPreferences2.lua] Themer-facing function for setting a user preference. - [03 Gameplay.lua] + [03 Gameplay.lua] [02 ActorDef.lua] Returns true if a decoration should be shown on the current screen or not. @@ -586,13 +586,13 @@ save yourself some time, copy this for undocumented things: [02 Colors.lua] - [02 ActorDef.lua] + [02 ActorDef.lua] - [02 ActorDef.lua] + [02 ActorDef.lua] - [02 ActorDef.lua] + [02 ActorDef.lua] [02 Utilities.lua] Returns a shuffled version of t. @@ -760,7 +760,7 @@ save yourself some time, copy this for undocumented things: Returns the zoom of a note for the provided PlayerState. - + Returns the FrameWidthScale of a hold part with a Y offset of fYOffset for the provided PlayerState.
fOverlappedTime is optional and will default to 0.
@@ -776,7 +776,7 @@ save yourself some time, copy this for undocumented things: of Enum. Instead of Enum.GetName( ) or - Enum.Reverse( + Enum.Reverse( ), one can use :GetName() or @@ -2353,6 +2353,61 @@ save yourself some time, copy this for undocumented things: Blanks the DifficultyIcon.
+ + + Return the width of the display in this mode. + + + Return the height of the display in this mode. + + + Return the refresh rate of the display in this mode. + + + + + An object describing a display: its supported and current video modes. + + + Return the unique identifier of the display. + + + Return the "human-readable" display name. + + + Return the s supported by this device. + + + Return the currently active for this display, + or nil if there is no such mode. + + + Return true if this DisplaySpec is describing the + "logical display" like an X screen or the Win32 "Virtual screen", or + false otherwise (if this describes a physical display). + + + + + An array-like userdata object which describes the displays + configured on the user's machine. + + This object supports the __len operator (#t), and + integer indexing, but does not support + iteration via pairs or ipairs. + + Each element of the array is a . + + + + Return the number of instances in the array. + + + Return the instance at index i + + + + Returns the index of the last banner loaded. @@ -2518,7 +2573,7 @@ save yourself some time, copy this for undocumented things: musicLength seconds one time. Both fadeIn and fadeOut can be customized as required. loop tells the sound manager to loop the music part. applyRate - tells the sound manager to apply the current music rate. If alignBeat + tells the sound manager to apply the current music rate. If alignBeat is true or nil, the length is automatically adjusted to cover an integer number of beats. @@ -2903,7 +2958,7 @@ save yourself some time, copy this for undocumented things: Sets if the Jukebox should use modifiers. - + Sets the number of multiplayer notefields to iFields @@ -3183,7 +3238,7 @@ save yourself some time, copy this for undocumented things: Selects a course. Returns false on failure. - + Returns a string from the specified element and value. @@ -4162,7 +4217,7 @@ save yourself some time, copy this for undocumented things: Returns true if pn's Profile was loaded from a memory card. - + Returns true if the last load of player pn's profile was a LastGood copy of the profile. @@ -4229,6 +4284,18 @@ save yourself some time, copy this for undocumented things: Return the cumulative FPS. + + Return an array-like userdata of type , + which describes the displays configured on the user's machine. + + + Return true if the current renderer supports render-to-texture, + false otherwise. + + + Return true if the current renderer/window implementation supports + a fullscreen borderless-window mode, false otherwise. + @@ -5516,7 +5583,7 @@ save yourself some time, copy this for undocumented things: Returns true if the theme has the specified metric.
- Returns true if the theme has the specified string. + Returns true if the theme has the specified string. Returns true if the specified theme is selectable. @@ -5525,7 +5592,7 @@ save yourself some time, copy this for undocumented things: Reloads the current theme's metrics. - + Changes the current theme.
diff --git a/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua b/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua index 20eef74c10..228773aacd 100644 --- a/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua +++ b/Docs/Themerdocs/Examples/OptionRowHandlerLua.lua @@ -59,13 +59,23 @@ function FooMods() return {PLAYER_2} end, + -- Optional function. If non-nil, this function must return the string representation + -- of a member of the ReloadChanged enum. This function is called in response to a message + -- listed in the optional ReloadRowMessages table below. For example, + -- to completely change the Choices presented by this OptionRow, modify + -- self.Choices in the body of this function and return "ReloadChanged_All" + Reload= function(self) + Trace("FooMods:Reload() called.") + return "ReloadChanged_None" + end, + -- A table of strings that are the names of choices. Choice names are not -- localized. Choices= {"a", "b", "c", "d"}, -- Optional table. If non-nil, this table must contain a list of messages -- this row should listen for. If one of the messages is recieved, the - -- row is reloaded (and the EnabledForPlayers function is called if it is + -- row is reloaded (and each of the EnabledForPlayers/Reload functions is called if it is -- non-nil). ReloadRowMessages= {"ReloadFooMods"}, @@ -90,6 +100,12 @@ function FooMods() -- SaveSelections should examine the list of what the player has selected -- and apply the appropriate modifiers to the player. -- Same args as LoadSelections. + -- + -- May optionally return an "effects" bit mask (constructed by combination of + -- the bit masks from the OptEffect enum), which dictates actions the + -- game should take in response to the selected option. For example, + -- OPT_SAVE_PREFERENCES directs the game to save Preferences.ini, + -- OPT_APPLY_ASPECT_RATIO directs the game to recompute the projection matrix, etc. SaveSelections= function(self, list, pn) Trace("FooMods:SaveSelections(" .. pn .. ")") for i, choice in ipairs(self.Choices) do diff --git a/StepmaniaCore.cmake b/StepmaniaCore.cmake index 6c94ae3681..852dc3e440 100644 --- a/StepmaniaCore.cmake +++ b/StepmaniaCore.cmake @@ -19,18 +19,21 @@ set(SM_EXE_NAME "StepMania") # Some OS specific helpers. if (CMAKE_SYSTEM_NAME MATCHES "Linux") set(LINUX TRUE) + set(SM_CPP_STANDARD "gnu++11") else() set(LINUX FALSE) endif() if (CMAKE_SYSTEM_NAME MATCHES "Darwin") set(MACOSX TRUE) + set(SM_CPP_STANDARD "gnu++14") else() set(MACOSX FALSE) endif() if (CMAKE_SYSTEM_NAME MATCHES "BSD") set(BSD TRUE) + set(SM_CPP_STANDARD "gnu++11") else() set(BSD FALSE) endif() @@ -279,8 +282,8 @@ elseif(MACOSX) # Apple Archs needs to be 32-bit for now. # When SDL2 is introduced, this may change. set(CMAKE_OSX_ARCHITECTURES "i386") - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.6") - set(CMAKE_OSX_DEPLOYMENT_TARGET_FULL "10.6.8") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7") + set(CMAKE_OSX_DEPLOYMENT_TARGET_FULL "10.7.1") find_library(MAC_FRAME_ACCELERATE Accelerate ${CMAKE_SYSTEM_FRAMEWORK_PATH}) find_library(MAC_FRAME_APPKIT AppKit ${CMAKE_SYSTEM_FRAMEWORK_PATH}) @@ -346,11 +349,18 @@ elseif(LINUX) find_package(Dl) - find_package(Xrandr) + find_package(Xrandr REQUIRED) if (${XRANDR_FOUND}) set(HAS_XRANDR TRUE) else() - set(HAX_XRANDR FALSE) + set(HAS_XRANDR FALSE) + endif() + + find_package(Xinerama) + if (${XINERAMA_FOUND}) + set(HAS_XINERAMA TRUE) + else() + set(HAS_XINERAMA FALSE) endif() find_package(PulseAudio) diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index f79bbf3270..9d4387b52e 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -397,6 +397,7 @@ DelayedTextureDelete=Set to ON hold textures in memory instead of freeing them. Delete= Difficulty= DisplayAspectRatio=Change the width-to-height ratio of graphics in the game. Most modern displays are either 16:9 or 16:10. +FullscreenType=Change whether fullscreen mode uses the display exclusively (traditional), or is a borderless window (nicer). DisplayColorDepth=Choose the color depth of the window. This option may not take effect in windowed mode. DisplayResolution=Choose the output resolution. A higher resolution will show more detail, but requires a faster video card. Display Options=BGFit, Appearance and UI options. @@ -1055,6 +1056,7 @@ DisplayAspectRatio=Aspect Ratio DisplayColorDepth=Display Color DisplayResolution=Display Resolution Display Options=Display Options +FullscreenType=Fullscreen Type Disqualification=Disqualification Duration seconds=Duration seconds EasterEggs=Easter Eggs diff --git a/Themes/_fallback/Scripts/02 Utilities.lua b/Themes/_fallback/Scripts/02 Utilities.lua index 6a3b60d3fe..0d9eced670 100644 --- a/Themes/_fallback/Scripts/02 Utilities.lua +++ b/Themes/_fallback/Scripts/02 Utilities.lua @@ -1,6 +1,6 @@ -- Find a key in tab with the given value. function FindValue(tab, value) - for key, name in tab do + for key, name in pairs(tab) do if value == name then return key end @@ -11,7 +11,7 @@ end -- Return the index of a true value in list. function FindSelection(list) - for index, on in list do + for index, on in ipairs(list) do if on then return index end @@ -32,7 +32,7 @@ end function wrap(val,n) local x = val Trace( "wrap "..x.." "..n ) - if x<0 then + if x<0 then x = x + (math.ceil(-x/n)+1)*n end Trace( "adjusted "..x ) @@ -122,18 +122,26 @@ end --Get the count of all items in a table function table.itemcount(t) local i = 0 - while next(t)~=nil do - i=i+1 + for _, v in pairs(t) do + i = i+1 end return i end function math.round(num, pre) if pre and pre < 0 then pre = 0 end - local mult = 10^(pre or 0) - if num >= 0 then return math.floor(num*mult+.5)/mult + local mult = 10^(pre or 0) + if num >= 0 then return math.floor(num*mult+.5)/mult else return math.ceil(num*mult-.5)/mult end end + +function math.gcd(a, b) + while b ~= 0 do + a, b = b, math.mod(a, b) + end + return a +end + -- deprecated? function round(val, decimal) if decimal then @@ -197,7 +205,7 @@ function ArgsIfPlayerJoinedOrNil(arg1,arg2) if arg1==nil then arg1=arg2 elseif arg2==nil then arg2=arg1 end return (GAMESTATE:IsSideJoined(PLAYER_1) and arg1 or nil),(GAMESTATE:IsSideJoined(PLAYER_2) and arg2 or nil) -end +end function Center1Player() local styleType = GAMESTATE:GetCurrentStyle():GetStyleType() @@ -345,16 +353,13 @@ local function round(num, idp) end function IsUsingWideScreen() - local curAspect = round(GetScreenAspectRatio(),5); - for k,v in pairs(AspectRatios) do - if AspectRatios[k] == curAspect then - if k == "SixteenNine" or k == "SixteenTen" then - return true; - else return false; - end; - end; - end; -end; + local curAspect = GetScreenAspectRatio() + if math.abs(curAspect-16/9) <= .044 or math.abs(curAspect - 16/10) <= .044 then + return true + else + return false + end +end -- Usage: Pass in an ActorFrame and a string to put in front of every line. -- indent will be appended to at each level of the recursion, to indent each @@ -511,7 +516,7 @@ end -- (c) 2005-2011 Glenn Maynard, Chris Danford, SSC -- All rights reserved. --- +-- -- 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 @@ -521,7 +526,7 @@ end -- copyright notice(s) and this permission notice appear in all copies of -- the Software and that both the above copyright notice(s) and this -- permission notice appear in supporting documentation. --- +-- -- 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 OF @@ -531,4 +536,3 @@ end -- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -- PERFORMANCE OF THIS SOFTWARE. - diff --git a/Themes/_fallback/Scripts/03 DisplaySpecs.lua b/Themes/_fallback/Scripts/03 DisplaySpecs.lua new file mode 100644 index 0000000000..e8782fc5ad --- /dev/null +++ b/Themes/_fallback/Scripts/03 DisplaySpecs.lua @@ -0,0 +1,614 @@ +-- A shim to make an honest-to-goodness lua table (array) out of the +-- userdata DisplaySpecs we get from c++ +local cachedSpecs = nil + +local function GetDisplaySpecs() + if cachedSpecs == nil then + local specs = DISPLAY:GetDisplaySpecs() + t = { + ById= function(self, id) + for i, d in ipairs(self) do + if d:GetId() == id or + ((id == '' or id == nil) and d:IsVirtual()) then + return self[i] + end + end + -- default to returning first DisplaySpec + return self[1] + end, + -- hold on to a reference to the userdata, so it isn't GC'd prematurely + c_specs= specs + } + for i = 1, #specs do + t[i] = specs[i] + end + + cachedSpecs = t + end + + return cachedSpecs +end + +-- Ease lookup of OptionRow effects masks +local OE = OptEffect:Reverse() + +function ConfDisplayMode() + local effectsMask = 2^OE["OptEffect_SavePreferences"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyGraphics"] + + local specs = GetDisplaySpecs() + local choices = {} + for i, d in ipairs(specs) do + choices[i] = d:GetName() + end + local origDisplay = PREFSMAN:GetPreference("DisplayId") + local origWindowed = PREFSMAN:GetPreference("Windowed") + choices[#choices + 1] = "Windowed" + + local t = { + Name= "Windowed", + GoToFirstOnStart= false, + OneChoiceForAllPlayers= true, + ExportOnChange= false, + LayoutType= "ShowAllInRow", + SelectType= "SelectOne", + + Choices= choices, + LoadSelections= function(self, list, pn) + local windowed = PREFSMAN:GetPreference("Windowed") + local displayIdx = FindValue(specs, specs:ById(PREFSMAN:GetPreference("DisplayId"))) + local i = windowed and #self.Choices or displayIdx + list[i] = true + end, + SaveSelections= function(self, list, pn) + local choice = FindSelection(list) + local selWindowed = choice == #self.Choices + local selDisplay = selWindowed and origDisplay or specs[choice]:GetId() + + -- If we've changed from the established preferences in place at our + -- instantiation, then we'll return our effects + if selWindowed ~= origWindowed + or selDisplay ~= origDisplay then + return effectsMask + else + return 0 + end + end, + NotifyOfSelection= function(self, pn, choice) + local curDisplay = PREFSMAN:GetPreference("DisplayId") + local curWindowed = PREFSMAN:GetPreference("Windowed") + local selWindowed = choice == #self.Choices + local selDisplay = selWindowed and curDisplay or specs[choice]:GetId() + -- If we've changed from what's currently saved in PREFSMAN, + -- then we'll broadcast that state + if selWindowed ~= curWindowed + or selDisplay ~= curDisplay then + PREFSMAN:SetPreference("Windowed", selWindowed) + PREFSMAN:SetPreference("DisplayId", selDisplay) + MESSAGEMAN:Broadcast("DisplayChoiceChanged") + end + end + } + return t +end + +-- TODO: here, I'm replicating the AspectRatios table from "02 Utilities.lua", +-- but in a friendly format +local winFracs = { + {n=3, d=4}, + {n=1, d=1}, + {n=5, d=4}, + {n=4, d=3}, + {n=16, d=10}, + {n=16, d=9}, + {n=8, d=3} +} +-- Maximum distance between aspect ratios to be considered equivalent +-- (approximately half the distance between 16:9 and 16:10) +local RATIO_EPSILON = .044 + + +local function GetWindowAspectRatios() + local ratios = {} + for i, f in ipairs(winFracs) do + ratios[math.round(f.n/f.d, 3)] = f + end + + return ratios +end + +-- build a map from DisplayId to supported aspect ratios +local function GetDisplayAspectRatios(specs) + local ratios = {} + local recognized = GetWindowAspectRatios() + for _, d in ipairs(specs) do + local dRatios = {} + for j, mode in ipairs(d:GetSupportedModes()) do + local trueRatio = mode:GetWidth()/mode:GetHeight() + local truncRatio = math.round(trueRatio, 3) + if not dRatios[truncRatio] then + local f = {} + -- Check if this is a known aspect ratio, or practically the + -- same (e.g. 1366x768 is typically considered 16:9) + for knownRatio, fraction in pairs(recognized) do + if math.abs(trueRatio - (fraction.n/fraction.d)) < RATIO_EPSILON then + truncRatio = knownRatio + f = fraction + break + end + end + + -- If we didn't find a recognized ratio, then register a new ratio + -- as the reduced fraction of width / height + if not f.n then + local w = mode:GetWidth() + local h = mode:GetHeight() + local gcd = math.gcd(w, h) + dRatios[trueRatio] = {n=w/gcd, d=h/gcd} + else + dRatios[truncRatio] = f + end + end + end + ratios[d:GetId()] = dRatios + -- Allow looking up the virtual display's aspect ratio by empty string + if d:IsVirtual() then + ratios[""] = dRatios + end + end + + return ratios +end + +-- hack, use as a space to share value between ConfAspectRatio() and +-- ConfDisplayResoltuion(). Can't use PREFSMAN:SetPreference("DisplayAspectRatio", x) +-- because that change take effect *immediately* (it is read in the rendering loop) +local CurAspectRatio = 0 + +function ConfAspectRatio() + local effectsMask = 2^OE["OptEffect_SavePreferences"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyGraphics"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyAspectRatio"] + local specs = GetDisplaySpecs() + local origAspectRatio = PREFSMAN:GetPreference("DisplayAspectRatio") + CurAspectRatio = origAspectRatio + + local dRatios = GetDisplayAspectRatios(specs) + local wRatios = GetWindowAspectRatios() + local t = { + Name= "DisplayAspectRatio", + GoToFirstOnStart= false, + OneChoiceForAllPlayers= true, + ExportOnChange= false, + LayoutType= "ShowAllInRow", + SelectType= "SelectOne", + ReloadRowMessages= {"DisplayChoiceChanged"}, + LoadSelections= function(self, list, pn) + local r = CurAspectRatio + for i, v in ipairs(self.ChoiceVals) do + if math.abs(CurAspectRatio - v) < RATIO_EPSILON then + list[i] = true + break + end + end + end, + + SaveSelections= function(self, list, pn) + local i = FindSelection(list) + PREFSMAN:SetPreference("DisplayAspectRatio", self.ChoiceVals[i]) + if math.abs(self.ChoiceVals[i] - origAspectRatio) < RATIO_EPSILON then + return 0 + else + return effectsMask + end + end, + + NotifyOfSelection= function(self, pn, choice) + local selRatio = self.ChoiceVals[choice] + if math.abs(selRatio-CurAspectRatio) >= RATIO_EPSILON then + CurAspectRatio = selRatio + MESSAGEMAN:Broadcast("AspectRatioChoiceChanged") + end + end, + + Reload= function(self) + local origVals = self.ChoiceVals + self:GenChoices() + + -- Pass along the message to ConfDisplayResolution() + -- regardless, but important we do this *after* self:GenChoices() + -- to ensure we've updated the CurAspectRatio in accordance + -- with what this display supports + MESSAGEMAN:Broadcast("AspectRatioChoiceChanged") + + if #origVals ~= #self.ChoiceVals then + return "ReloadChanged_All" + end + + for i, v in ipairs(origVals) do + if origVals[i] ~= self.ChoiceVals[i] then + return "ReloadChanged_All" + end + end + + return "ReloadChanged_None" + end, + + GenChoices= function(self) + -- Determine the available aspect ratios for the current display mode + local isWindowed = PREFSMAN:GetPreference("Windowed") + local curDisplayId = PREFSMAN:GetPreference("DisplayId") + local ratios = isWindowed and wRatios or (dRatios[curDisplayId] or dRatios[specs[1]:GetId()]) + local choices = {} + local vals = {} + foreach_ordered(ratios, function(v, f) + vals[#vals + 1] = f.n/f.d + choices[#choices + 1] = tostring(f.n) .. ':' .. tostring(f.d) + end) + self.Choices = choices + self.ChoiceVals = vals + + -- Get the closest aspect ratio to the currently-configured one + -- that this display mode supports + local closestRatio = vals[1] + local closestDist = math.abs(vals[1] - CurAspectRatio) + for _, v in ipairs(vals) do + local dist = math.abs(v - CurAspectRatio) + if dist < closestDist then + closestRatio = v + closestDist = dist + end + end + CurAspectRatio = closestRatio + end + } + t:GenChoices() + return t +end + +local function GenerateFeasibleWindowSizesForRatio(specs, r) + -- Get the largest feasible window size *for a single monitor*, + -- then generate a sequence of smaller window sizes, until + -- one of height/width < 300 (arbitrary) + -- TODO: Get fancy and consider larger rectangles that span + -- multiple monitors + local maxWinWidth = 0 + for _, d in ipairs(specs) do + local modes = d:GetSupportedModes() + local dLargestMode = modes[#modes] + local w = dLargestMode:GetWidth() + local h = dLargestMode:GetHeight() + local dMaxWinWidth = w/r <= h and w or r*h + maxWinWidth = dMaxWinWidth > maxWinWidth and dMaxWinWidth or maxWinWidth + end + + -- Generate successively smaller rectangles + local sizes = {} + + local w = maxWinWidth + local h = maxWinWidth/r + while w > 300 and h > 300 do + sizes[#sizes + 1] = {w=math.round(w), h=math.round(h)} + w = 0.75*w + h = w/r + end + + -- Reverse the list (sort in increasing size) + local rsizes = {} + for i = #sizes, 1, -1 do + rsizes[i] = sizes[#sizes - i + 1] + end + + return rsizes +end + +local function GetFeasibleWindowSizesForRatio(specs, r) + -- First, try to see if we have a sufficiently large set of + -- resolutions to pick from in the list(s) of supported modes for the display(s) + -- (since those resolutions tend to be "natural" looking), otherwise just generate + -- some window sizes (which might look weird). + local widths = {} + for _, d in ipairs(specs) do + for _, m in ipairs(d:GetSupportedModes()) do + local w = m:GetWidth() + local h = m:GetHeight() + if math.abs((w/h) - r) < RATIO_EPSILON then + widths[w] = h + end + end + end + + if table.itemcount(widths) < 4 then + return GenerateFeasibleWindowSizesForRatio(specs, r) + else + local resolutions = {} + for w, h in pairs(widths) do + resolutions[#resolutions + 1] = {w=w, h=h} + end + table.sort(resolutions, function (a, b) return a.w < b.w end) + return resolutions + end + +end + + +local function GetDisplayResolutionsForRatio(d, r) + local sizes = {} + for _, m in ipairs(d:GetSupportedModes()) do + local w = m:GetWidth() + local h = m:GetHeight() + local trueRatio = w/h + if math.abs(trueRatio - r) < RATIO_EPSILON then + -- This depends on the fact that GetSupportedModes returns + -- a *sorted* list + if #sizes == 0 or sizes[#sizes].w ~= w or sizes[#sizes].h ~= h then + sizes[#sizes + 1] = {w=m:GetWidth(), h=m:GetHeight()} + end + end + end + return sizes +end + +function ConfDisplayResolution() + local effectsMask = 2^OE["OptEffect_SavePreferences"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyGraphics"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyAspectRatio"] + local specs = GetDisplaySpecs() + local origWidth = PREFSMAN:GetPreference("DisplayWidth") + local origHeight = PREFSMAN:GetPreference("DisplayHeight") + + local t = { + Name= "DisplayResolution", + GoToFirstOnStart= false, + OneChoiceForAllPlayers= true, + ExportOnChange= false, + LayoutType= "ShowAllInRow", + SelectType= "SelectOne", + ReloadRowMessages= {"AspectRatioChoiceChanged"}, + LoadSelections= function(self, list, pn) + local w = PREFSMAN:GetPreference("DisplayWidth") + local h = PREFSMAN:GetPreference("DisplayHeight") + for i, v in ipairs(self.ChoiceVals) do + if v.w == w and v.h == h then + list[i] = true + break + end + end + end, + + SaveSelections= function(self, list, pn) + local i = FindSelection(list) + local w = self.ChoiceVals[i].w + local h = self.ChoiceVals[i].h + PREFSMAN:SetPreference("DisplayWidth", w) + PREFSMAN:SetPreference("DisplayHeight", h) + if w == origWidth and h == origHeight then + return 0 + else + return effectsMask + end + end, + + NotifyOfSelection= function(self, pn, choice) + local selRes = self.ChoiceVals[choice] + local curWidth = PREFSMAN:GetPreference("DisplayWidth") + local curHeight = PREFSMAN:GetPreference("DisplayHeight") + if selRes.w ~= curWidth or selRes.h ~= curHeight then + PREFSMAN:SetPreference("DisplayWidth", selRes.w) + PREFSMAN:SetPreference("DisplayHeight", selRes.h) + MESSAGEMAN:Broadcast("DisplayResolutionChoiceChanged") + end + end, + + Reload= function(self) + local origVals = self.ChoiceVals + self:GenChoices() + + -- Pass along the message to ConfRefreshRate() regardless of + -- what happens here. Do this *after* GenChoices() so that + -- *if* currently configured resolution needed to change in response + -- to change in configured AspectRatio, ConfRefreshRate() can change + -- accordingly + MESSAGEMAN:Broadcast("DisplayResolutionChoiceChanged") + + if #origVals ~= #self.ChoiceVals then + return "ReloadChanged_All" + end + + for i, v in ipairs(origVals) do + if origVals[i] ~= self.ChoiceVals[i] then + return "ReloadChanged_All" + end + end + + return "ReloadChanged_None" + end, + + GenChoices= function(self) + local isWindowed = PREFSMAN:GetPreference("Windowed") + local curDisplay = specs:ById(PREFSMAN:GetPreference("DisplayId")) + local curRatio = CurAspectRatio ~= 0 and CurAspectRatio or PREFSMAN:GetPreference("DisplayAspectRatio") + local resolutions = isWindowed and GetFeasibleWindowSizesForRatio(specs, curRatio) + or GetDisplayResolutionsForRatio(curDisplay, curRatio) + + local choices = {} + local vals = {} + for i, r in ipairs(resolutions) do + vals[#vals + 1] = r + choices[#choices + 1] = tostring(r.w) .. 'x' .. tostring(r.h) + end + self.Choices = choices + self.ChoiceVals = vals + + -- If the currently configured resolution isn't available at this + -- aspect ratio, then pick the closest resolution + local w = PREFSMAN:GetPreference("DisplayWidth") + local h = PREFSMAN:GetPreference("DisplayHeight") + local closest = nil + local mindist = -1 + for i, v in ipairs(vals) do + local dist = math.sqrt((v.w - w)^2 + (v.h - h)^2) + if mindist == -1 or dist < mindist then + mindist = dist + closest = v + end + end + PREFSMAN:SetPreference("DisplayWidth", closest.w) + PREFSMAN:SetPreference("DisplayHeight", closest.h) + end + } + t:GenChoices() + return t +end + +function GetDisplayRatesForResolution(d, w, h) + local rates = {} + for _, m in ipairs(d:GetSupportedModes()) do + if m:GetWidth() == w and m:GetHeight() == h then + rates[#rates + 1] = math.round(m:GetRefreshRate()) + end + end + return rates +end + +function ConfRefreshRate() + local effectsMask = 2^OE["OptEffect_SavePreferences"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyGraphics"] + local specs = GetDisplaySpecs() + local origRate = PREFSMAN:GetPreference("RefreshRate") + + local t = { + Name= "RefreshRate", + GoToFirstOnStart= false, + OneChoiceForAllPlayers= true, + ExportOnChange= false, + LayoutType= "ShowAllInRow", + SelectType= "SelectOne", + ReloadRowMessages= {"DisplayResolutionChoiceChanged"}, + LoadSelections= function(self, list, pn) + local r = PREFSMAN:GetPreference("RefreshRate") + local i = FindValue(self.ChoiceVals, r) + list[i] = true + end, + + SaveSelections= function(self, list, pn) + local i = FindSelection(list) + local r = self.ChoiceVals[i] + PREFSMAN:SetPreference("RefreshRate", r) + if r ~= origRate then + return effectsMask + else + return 0 + end + end, + + NotifyOfSelection= function(self, pn, choice) + local selRate = self.ChoiceVals[choice] + local curRate = PREFSMAN:GetPreference("RefreshRate") + if selRate ~= curRate then + PREFSMAN:SetPreference("RefreshRate", selRate) + end + end, + + Reload= function(self) + local origVals = self.ChoiceVals + self:GenChoices() + if #origVals ~= #self.ChoiceVals then + return "ReloadChanged_All" + end + + for i, v in ipairs(origVals) do + if origVals[i] ~= self.ChoiceVals[i] then + return "ReloadChanged_All" + end + end + + return "ReloadChanged_None" + end, + + GenChoices= function(self) + local isWindowed = PREFSMAN:GetPreference("Windowed") + local d = specs:ById(PREFSMAN:GetPreference("DisplayId")) + local w = PREFSMAN:GetPreference("DisplayWidth") + local h = PREFSMAN:GetPreference("DisplayHeight") + local rates = isWindowed and {} + or GetDisplayRatesForResolution(d, w, h) + + local choices = {"Default"} + local choiceVals = {REFRESH_DEFAULT} + for i, r in ipairs(rates) do + choiceVals[#choiceVals + 1] = math.round(r) + choices[#choices + 1] = tostring(math.round(r)) + end + + self.Choices = choices + self.ChoiceVals = choiceVals + + -- If the currently configured refresh rate isn't available at this + -- resolution, then pick the closest one (so long as it's within + -- a fixed threshold) + local curRate = PREFSMAN:GetPreference("RefreshRate") + local threshold = 10 + local closestIdx = nil + local mindist = -1 + for i, r in ipairs(choiceVals) do + local dist = math.abs(r - curRate) + if mindist == -1 or dist < mindist then + mindist = dist + closestIdx = i + end + end + local curRateIdx = mindist < threshold and closestIdx or 1 + PREFSMAN:SetPreference("RefreshRate", choiceVals[curRateIdx]) + end + } + t:GenChoices() + return t +end + +function ConfFullscreenType() + local effectsMask = 2^OE["OptEffect_SavePreferences"] + effectsMask = effectsMask + 2^OE["OptEffect_ApplyGraphics"] + + local FULLSCREEN_EXCLUSIVE = 0 + local FULLSCREEN_BORDERLESS_WIN = 1 + + local fsbwSupported = DISPLAY:SupportsFullscreenBorderlessWindow() + + local choiceVals = {FULLSCREEN_EXCLUSIVE} + local choices = {"Default"} + if fsbwSupported then + choices = {"Exclusive", "Borderless Window"} + choiceVals = {FULLSCREEN_EXCLUSIVE, FULLSCREEN_BORDERLESS_WIN} + end + local origFSType = PREFSMAN:GetPreference("FullscreenIsBorderlessWindow") and FULLSCREEN_BORDERLESS_WIN or FULLSCREEN_EXCLUSIVE + + local t = { + Name= "FullscreenType", + GoToFirstOnStart= false, + OneChoiceForAllPlayers= true, + ExportOnChange= false, + LayoutType= "ShowAllInRow", + SelectType= "SelectOne", + Choices= choices, + ChoiceVals= choiceVals, + + LoadSelections= function(self, list, pn) + local fsType = PREFSMAN:GetPreference("FullscreenIsBorderlessWindow") and FULLSCREEN_BORDERLESS_WIN + or FULLSCREEN_EXCLUSIVE + local i = FindValue(self.ChoiceVals, fsType) + list[i] = true + end, + SaveSelections= function(self, list, pn) + local selFSType = self.ChoiceVals[FindSelection(list)] + PREFSMAN:SetPreference("FullscreenIsBorderlessWindow", selFSType == FULLSCREEN_BORDERLESS_WIN) + if selFSType ~= origFSType then + return effectsMask + else + return 0 + end + end + } + + return t +end diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index ec5b7233fb..2abfd26d86 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -3051,10 +3051,12 @@ Line22="conf,EventMode" Fallback="ScreenOptionsServiceChild" NextScreen="ScreenOptionsService" PrevScreen="ScreenOptionsService" -LineNames="1,2,3,4,5,6,7,8,9,10,11,12,13,FNR,14,17,18,19,20" -Line1="conf,Windowed" -Line2="conf,DisplayResolution" -Line3="conf,DisplayAspectRatio" +LineNames="1,2,3,RefreshRate,FSType,4,5,6,7,8,9,10,11,13,FNR,14,17,18,19,20" +Line1="lua,ConfDisplayMode()" +Line2="lua,ConfAspectRatio()" +Line3="lua,ConfDisplayResolution()" +LineRefreshRate="lua,ConfRefreshRate()" +LineFSType="lua,ConfFullscreenType()" Line4="conf,DisplayColorDepth" Line5="conf,HighResolutionTextures" Line6="conf,MaxTextureResolution" @@ -3063,6 +3065,7 @@ Line8="conf,MovieColorDepth" Line9="conf,SmoothLines" Line10="conf,CelShadeModels" Line11="conf,DelayedTextureDelete" +# RefreshRate ConfOption no longer used Line12="conf,RefreshRate" Line13="conf,Vsync" LineFNR="conf,FastNoteRendering" diff --git a/autoconf/m4/x11.m4 b/autoconf/m4/x11.m4 index 2ce84924f6..095142bfa2 100644 --- a/autoconf/m4/x11.m4 +++ b/autoconf/m4/x11.m4 @@ -33,9 +33,7 @@ AC_DEFUN([SM_X11], fi # Check for Xrandr - # Can someone fix this for me? This is producing bizarre warnings from - # configure... I have no clue what I'm doing -Ben - AC_CHECK_LIB(Xrandr, XRRSizes, + AC_CHECK_LIB(Xrandr, XRRQueryVersion, have_xrandr=yes, have_xrandr=no, [$XLIBS]) @@ -45,14 +43,36 @@ AC_DEFUN([SM_X11], have_xrandr=no fi - if test "$have_xrandr" = "no"; then + # Check for Xxf86vm + AC_CHECK_LIB(Xxf86vm, XF86VidModeSwitchToMode, + have_xf86vm=yes, + have_xf86vm=no, + [$XLIBS]) + AC_CHECK_HEADER(X11/extensions/xf86vmode.h, have_xf86vm_header=yes, have_xf86vm_header=no, [#include ]) + + if test "$have_xf86vm_header" = "no"; then + have_xf86vm=no + fi + + no_modeset=yes + if test "$have_xrandr" = "yes"; then + XLIBS="$XLIBS -lXrandr" + AC_DEFINE(HAVE_XRANDR, 1, [Xrandr is available]) + no_modeset=no + fi + + if test "$have_xf86vm" = "yes"; then + XLIBS="$XLIBS -lXxf86vm" + AC_DEFINE(HAVE_XF86VIDMODE, 1, [XF86VidMode is available]) + no_modeset=no + fi + + if test "$no_modeset" = "yes"; then if test "$unix" = "yes"; then AC_MSG_ERROR("Couldn't find X11 libraries.") else no_x=yes fi - else - XLIBS="$XLIBS -lXrandr" fi AM_CONDITIONAL(HAVE_X11, test "$no_x" != "yes") diff --git a/extern/CMakeProject-json.cmake b/extern/CMakeProject-json.cmake index 5d35a9608e..0b8fba9d88 100644 --- a/extern/CMakeProject-json.cmake +++ b/extern/CMakeProject-json.cmake @@ -20,11 +20,21 @@ add_library("jsoncpp" STATIC ${JSON_SRC} ${JSON_HPP}) set_property(TARGET "jsoncpp" PROPERTY FOLDER "External Libraries") -if(MSVC) - sm_add_compile_definition("jsoncpp" _CRT_SECURE_NO_WARNINGS) -endif() - disable_project_warnings("jsoncpp") target_include_directories("jsoncpp" PUBLIC "jsoncpp/include") +if(MSVC) + sm_add_compile_definition("jsoncpp" _CRT_SECURE_NO_WARNINGS) +elseif(APPLE) + set_target_properties("jsoncpp" PROPERTIES + XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "gnu++14" + XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++" + ) +else() # Unix/Linux + sm_add_compile_flag("jsoncpp" "-std=gnu++11") + if (CMAKE_CXX_COMPILER MATCHES "clang") + sm_add_compile_flag("jsoncpp" "-stdlib=libc++") + endif() +endif() + diff --git a/src/Actor.cpp b/src/Actor.cpp index a2623b02f6..fc8689bb57 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -258,6 +258,79 @@ Actor::Actor( const Actor &cpy ): #undef CPY } +Actor &Actor::operator=(Actor other) +{ + /* Don't copy an Actor in the middle of rendering. */ + ASSERT( other.m_pTempState == nullptr ); + m_pTempState = nullptr; + + using std::swap; +#define SWAP(x) swap(x, other.x) + SWAP( m_sName ); + SWAP( m_pParent ); + SWAP( m_FakeParent ); + SWAP( m_pLuaInstance ); + + SWAP( m_WrapperStates ); + + SWAP( m_baseRotation ); + SWAP( m_baseScale ); + SWAP( m_fBaseAlpha ); + SWAP( m_internalDiffuse ); + SWAP( m_internalGlow ); + + + SWAP( m_size ); + SWAP( m_current ); + SWAP( m_start ); + SWAP( m_Tweens ); + + SWAP( m_bFirstUpdate ); + + SWAP( m_fHorizAlign ); + SWAP( m_fVertAlign ); +#if defined(SSC_FUTURES) + SWAP( M_Effects ); +#else + SWAP( m_Effect ); +#endif + SWAP( m_fSecsIntoEffect ); + SWAP( m_fEffectDelta ); + SWAP(m_effect_ramp_to_half); + SWAP(m_effect_hold_at_half); + SWAP(m_effect_ramp_to_full); + SWAP(m_effect_hold_at_full); + SWAP(m_effect_hold_at_zero); + SWAP(m_effect_period); + SWAP( m_fEffectOffset ); + SWAP( m_EffectClock ); + + SWAP( m_effectColor1 ); + SWAP( m_effectColor2 ); + SWAP( m_vEffectMagnitude ); + + SWAP( m_bVisible ); + SWAP( m_fHibernateSecondsLeft ); + SWAP( m_fShadowLengthX ); + SWAP( m_fShadowLengthY ); + SWAP( m_ShadowColor ); + SWAP( m_bIsAnimating ); + SWAP( m_iDrawOrder ); + + SWAP( m_bTextureWrapping ); + SWAP( m_bTextureFiltering ); + SWAP( m_BlendMode ); + SWAP( m_bClearZBuffer ); + SWAP( m_ZTestMode ); + SWAP( m_bZWrite ); + SWAP( m_fZBias ); + SWAP( m_CullMode ); + + SWAP( m_mapNameToCommands ); +#undef SWAP + return *this; +} + /* XXX: This calls InitCommand, which must happen after all other * initialization (eg. ActorFrame loading children). However, it * also loads input variables, which should happen first. The diff --git a/src/Actor.h b/src/Actor.h index 570ca59dc0..c6455e979b 100644 --- a/src/Actor.h +++ b/src/Actor.h @@ -111,6 +111,7 @@ public: * @brief Copy a new Actor to the old one. * @param cpy the new Actor to use in place of this one. */ Actor( const Actor &cpy ); + Actor &operator=( Actor other ); virtual ~Actor(); virtual Actor *Copy() const; virtual void InitState(); diff --git a/src/CMakeData-data.cmake b/src/CMakeData-data.cmake index eddde6b2d4..481b154eaf 100644 --- a/src/CMakeData-data.cmake +++ b/src/CMakeData-data.cmake @@ -167,6 +167,7 @@ list(APPEND SM_DATA_REST_SRC "CryptHelpers.cpp" "DateTime.cpp" "Difficulty.cpp" + "DisplaySpec.cpp" "EnumHelper.cpp" "FileDownload.cpp" "Game.cpp" @@ -218,7 +219,7 @@ list(APPEND SM_DATA_REST_HPP "CryptHelpers.h" "CubicSpline.h" "DateTime.h" - "DisplayResolutions.h" + "DisplaySpec.h" "Difficulty.h" "EnumHelper.h" "FileDownload.h" diff --git a/src/CMakeData-gtk.cmake b/src/CMakeData-gtk.cmake index 7082744c79..836904d913 100644 --- a/src/CMakeData-gtk.cmake +++ b/src/CMakeData-gtk.cmake @@ -8,6 +8,12 @@ add_library("GtkModule" "arch/LoadingWindow/LoadingWindow_GtkModule.h" ) +sm_add_compile_flag("GtkModule" "-std=${SM_CPP_STANDARD}") +if (CMAKE_CXX_COMPILER MATCHES "clang") + sm_add_compile_flag("GtkModule" "-stdlib=libc++") + set_target_properties("GtkModule" PROPERTIES LINK_FLAGS "-stdlib=libc++") +endif() + # It is normally not appropriate to set the prefix to the empty string. # This is to maintain compatibility with the current source. # At some point, it may be worth being more flexible. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3c0cf81b4b..f521143d0b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -165,7 +165,7 @@ if(WIN32) sm_add_compile_definition("${SM_EXE_NAME}" _CRT_SECURE_NO_WARNINGS) sm_add_compile_definition("${SM_EXE_NAME}" _WINSOCK_DEPRECATED_NO_WARNINGS) sm_add_compile_definition("${SM_EXE_NAME}" GLEW_STATIC) - + set_target_properties("${SM_EXE_NAME}" PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${SM_PROGRAM_DIR}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${SM_PROGRAM_DIR}" @@ -173,7 +173,7 @@ if(WIN32) RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${SM_PROGRAM_DIR}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${SM_PROGRAM_DIR}" ) - + if(MSVC) # Allow for getting a virtualdub stack trace. add_custom_command(TARGET "${SM_EXE_NAME}" @@ -190,6 +190,8 @@ elseif(APPLE) RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${SM_ROOT_DIR}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${SM_ROOT_DIR}" MACOSX_BUNDLE_INFO_PLIST "${SM_XCODE_DIR}/Info.StepMania.plist" + XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "${SM_CPP_STANDARD}" + XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++" XCODE_ATTRIBUTE_INFOPLIST_FILE "${SM_XCODE_DIR}/Info.StepMania.plist" XCODE_ATTRIBUTE_INFOPLIST_PREPROCESS "YES" XCODE_ATTRIBUTE_INFOPLIST_PREPROCESSOR_DEFINITIONS[variant=Release] "RELEASE" @@ -198,6 +200,7 @@ elseif(APPLE) XCODE_ATTRIBUTE_INFOPLIST_PREPROCESSOR_DEFINITIONS[variant=RelWithDebInfo] "RELWITHDEBINFO" XCODE_ATTRIBUTE_INFOPLIST_PREFIX_HEADER "${SM_XCODE_DIR}/plistHelper.hpp" XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/archutils/Darwin/StepMania.pch" + XCODE_ATTRIBUTE_GCC_ENABLE_CPP_EXCEPTIONS "NO" XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS "${SM_XCODE_DIR}/Libraries" ) @@ -304,6 +307,13 @@ else() # Linux if (${HAS_FFMPEG}) sm_add_compile_definition("${SM_EXE_NAME}" HAVE_FFMPEG) endif() + if (${HAS_XRANDR}) + sm_add_compile_definition("${SM_EXE_NAME}" HAVE_XRANDR) + endif() + if (${HAS_XINERAMA} AND WITH_XINERAMA) + sm_add_compile_definition("${SM_EXE_NAME}" HAVE_XINERAMA) + endif() + if (BSD) sm_add_compile_definition("${SM_EXE_NAME}" BSD) endif() @@ -316,6 +326,12 @@ else() # Linux if("${CMAKE_SYSTEM}" MATCHES "Linux") sm_add_compile_definition("${SM_EXE_NAME}" LINUX) endif() + + sm_add_compile_flag("${SM_EXE_NAME}" "-std=${SM_CPP_STANDARD}") + if (CMAKE_CXX_COMPILER MATCHES "clang") + sm_add_compile_flag("${SM_EXE_NAME}" "-stdlib=libc++") + set_target_properties("${SM_EXE_NAME}" PROPERTIES LINK_FLAGS "-stdlib=libc++") + endif() endif() set_property(TARGET "${SM_EXE_NAME}" PROPERTY FOLDER "Internal Libraries") @@ -346,7 +362,7 @@ if (NOT SYSTEM_PCRE_FOUND) endif() if (WIN32) - + list(APPEND SMDATA_LINK_LIB # The misc libraries are here. "${LIB_SWSCALE}" @@ -354,7 +370,7 @@ if (WIN32) "${LIB_AVFORMAT}" "${LIB_AVUTIL}" ) - + if (WITH_OGG) list(APPEND SMDATA_LINK_LIB "ogg" @@ -362,15 +378,15 @@ if (WIN32) "vorbisfile" ) endif() - + list(APPEND SMDATA_LINK_LIB "dbghelp.lib" "setupapi.lib" "hid.lib" ) - + get_filename_component(DIRECTX_LIBRARY_DIR "${DIRECTX_LIBRARIES}" DIRECTORY) - + sm_add_link_flag("${SM_EXE_NAME}" "/LIBPATH:\"${DIRECTX_LIBRARY_DIR}\"") sm_add_link_flag("${SM_EXE_NAME}" "/LIBPATH:\"${SM_EXTERN_DIR}/ffmpeg/lib\"") sm_add_link_flag("${SM_EXE_NAME}" "/LIBPATH:\"${SM_SRC_DIR}/archutils/Win32/ddk\"") @@ -385,7 +401,7 @@ if (WIN32) set_target_properties("${SM_EXE_NAME}" PROPERTIES LINK_FLAGS_DEBUG "/NODEFAULTLIB:msvcrt.lib") set_target_properties("${SM_EXE_NAME}" PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") set_target_properties("${SM_EXE_NAME}" PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") - + elseif(APPLE) # The following were removed from SMDATA_LINK_LIB to "match" StepMania's pbxproj. # ${MAC_FRAME_APPKIT} @@ -508,6 +524,7 @@ else() # Unix / Linux list(APPEND SMDATA_LINK_LIB ${XRANDR_LIBRARIES} + ${XINERAMA_LIBRARIES} ) list(REMOVE_ITEM SMDATA_LINK_LIB "zlib") diff --git a/src/DisplaySpec.cpp b/src/DisplaySpec.cpp new file mode 100644 index 0000000000..c5e29bfe4b --- /dev/null +++ b/src/DisplaySpec.cpp @@ -0,0 +1,149 @@ +#include "global.h" +#include "DisplaySpec.h" +#include "LuaBinding.h" +#include "RageLog.h" + +class LunaDisplayMode: public Luna +{ +public: + DEFINE_METHOD( GetWidth, width ); + DEFINE_METHOD( GetHeight, height ); + DEFINE_METHOD( GetRefreshRate, refreshRate ); + LunaDisplayMode() + { + ADD_METHOD( GetWidth ); + ADD_METHOD( GetHeight ); + ADD_METHOD( GetRefreshRate ); + } +}; + +LUA_REGISTER_CLASS( DisplayMode ) + +class LunaDisplaySpec: public Luna +{ +public: + DEFINE_METHOD( GetId, id() ); + DEFINE_METHOD( GetName, name() ); + DEFINE_METHOD( IsVirtual, isVirtual() ); + static int GetSupportedModes( T* p, lua_State *L) + { + std::vector v; + for (auto const &m: p->supportedModes()) + { + v.push_back( const_cast(&m)); + } + LuaHelpers::CreateTableFromArray( v, L ); + return 1; + } + static int GetCurrentMode( T *p, lua_State *L ) + { + if (p->currentMode() != NULL) { + DisplayMode *m = const_cast( p->currentMode() ); + m->PushSelf( L ); + } else { + lua_pushnil( L ); + } + return 1; + } + + LunaDisplaySpec() + { + ADD_METHOD( GetId ); + ADD_METHOD( GetName ); + ADD_METHOD( GetSupportedModes ); + ADD_METHOD( IsVirtual ); + ADD_METHOD( GetCurrentMode ); + } +}; + +LUA_REGISTER_CLASS( DisplaySpec ) + +namespace +{ + const char *DISPLAYSPECS = "DisplaySpecs"; + + DisplaySpecs *check_DisplaySpecs(lua_State *L) + { + void *ud = luaL_checkudata( L, 1, DISPLAYSPECS ); + luaL_argcheck( L, ud != NULL, 1, "`DisplaySpecs` expected" ); + return static_cast(ud); + } + + int DisplaySpecs_gc(lua_State *L) + { + DisplaySpecs *specs = static_cast (lua_touserdata( L, 1 )); + if (specs) + { + specs->~DisplaySpecs(); + } + + return 0; + } + + int DisplaySpecs_len(lua_State *L) + { + DisplaySpecs *specs = check_DisplaySpecs( L ); + if (specs) + { + lua_pushinteger( L, specs->size() ); + } else + { + lua_pushinteger( L, 0 ); + } + return 1; + } + + int DisplaySpecs_tostring(lua_State *L) + { + DisplaySpecs *specs = check_DisplaySpecs( L ); + lua_pushfstring( L, "DisplaySpecs: %p", specs ); + return 1; + } + + int DisplaySpecs_get(lua_State *L) + { + DisplaySpecs *specs = check_DisplaySpecs( L ); + if (specs) + { + int index = luaL_checkint( L, 2 ); + luaL_argcheck( L, 1 <= index && static_cast (index) <= specs->size(), 2, + "index out of range" ); + DisplaySpecs::iterator it = specs->begin(); + std::advance( it, index - 1 ); + DisplaySpec *s = const_cast(&(*it)); + s->PushSelf( L ); + } + else + { + lua_pushnil( L ); + } + return 1; + } + + const luaL_Reg DisplaySpecs_meta[] = + { + {"__gc", DisplaySpecs_gc}, + {"__index", DisplaySpecs_get}, + {"__len", DisplaySpecs_len}, + {"__tostring", DisplaySpecs_tostring}, + {NULL, NULL} + }; + + + void register_DisplaySpecs(lua_State *L) + { + luaL_newmetatable( L, DISPLAYSPECS ); + luaL_openlib( L, 0, DisplaySpecs_meta, 0 ); + lua_pop( L, 1 ); + } +} +REGISTER_WITH_LUA_FUNCTION(register_DisplaySpecs); + +DisplaySpecs *pushDisplaySpecs(lua_State *L, const DisplaySpecs &specs) +{ + void *vpSpecs = lua_newuserdata( L, sizeof( DisplaySpecs )); + DisplaySpecs *pspecs = new( vpSpecs ) DisplaySpecs( specs ); + luaL_getmetatable( L, DISPLAYSPECS ); + lua_setmetatable( L, -2 ); + return pspecs; +} diff --git a/src/DisplaySpec.h b/src/DisplaySpec.h new file mode 100644 index 0000000000..4ff7a11bdd --- /dev/null +++ b/src/DisplaySpec.h @@ -0,0 +1,203 @@ +#ifndef DisplaySpec_H +#define DisplaySpec_H + +#include + +#include "RageTypes.h" + +struct DisplayMode { + // Width (in pixels) of the display in this mode + unsigned int width; + // Height (in pixels) of the display in this mode + unsigned int height; + /** Refresh rate, in hz, of the display for the given mode + * MacOS Quartz Display Services provides rate as double, + * winapi DEVMODE uses 32bit unsigned int (still gives rate as hz), + * RandR gives 32 bit pixel clock, which is then divided by width*height. + */ + double refreshRate; + /* + * Bits-per-pixel is *not* a property of the DisplayMode for our purposes: + * bit-depth is going to be a property of the OpenGL/D3D context, not a display + * configuration + */ + + bool operator<( const DisplayMode &other ) const + { +/** @brief A quick way to compare the two DisplayResolutions. */ +#define COMPARE(x) if( x != other.x ) return x < other.x; + COMPARE( width ); + COMPARE( height ); + COMPARE( refreshRate ); +#undef COMPARE + return false; + } + + // Lua + void PushSelf( lua_State *L ); +}; + +/** @brief The dimensions of the program. */ +class DisplaySpec +{ +public: + /* + * Construct a specification for the display with the given ID, which supports the given modes, + * and is currently using the specified mode with the specified logical screen bounds + */ + DisplaySpec(const std::string &id, + const std::string &name, + const std::set &modes, + const DisplayMode &curMode, + const RectI &curBounds, + const bool isVirtual=false): + m_sId( id ), m_sName( name ), + m_sModes( modes ), m_bCurModeActive( true ), m_CurMode( curMode ), + m_rectBounds( curBounds ), m_bIsVirtual( isVirtual ) + { + if ( m_sModes.find( curMode ) == m_sModes.end() ) + { + // This is an error, make a failing assertion with a descriptive error message + std::stringstream msgStream; + msgStream << "DisplaySpec current mode (" << curMode.width << "x" << + curMode.height << "@" << curMode.refreshRate << ") not in given list of supported modes: "; + for ( auto &m : modes ) + { + msgStream << m.width << "x" << m.height << "@" << m.refreshRate << ", "; + } + auto msg = msgStream.str(); + // Drop the trailing ", " + msg.resize( msg.size() - 2 ); + + ASSERT_M( false, msg.c_str() ); + } + } + + /* + * Construct a specification for the display with the given ID, which supports the given modes, + * and is currently disabled (has no active mode) + */ + DisplaySpec(const std::string id, + const std::string name, + const std::set modes, + const bool isVirtual=false): + m_sId( id ), m_sName( name ), + m_sModes( modes ), m_bCurModeActive( false ), m_CurMode( { } ), + m_bIsVirtual( isVirtual ) + { + + } + + // Create a specification for a display supporting a single (and currently active) mode + DisplaySpec(std::string id, std::string name, DisplayMode mode) : m_sId( id ), m_sName( name ), m_bIsVirtual( false ), + m_bCurModeActive( true ), m_CurMode( mode ) + { + m_sModes.insert( mode ); + m_rectBounds = RectI( 0, 0, mode.width, mode.height ); + } + + DisplaySpec( const DisplaySpec &other ) = default; + + std::string name() const + { + return m_sName; + } + + std::string id() const + { + return m_sId; + } + + const std::set &supportedModes() const + { + return m_sModes; + } + + /* + * Return a pointer to the currently active display mode, or NULL if + * display is inactive + * + * Note that inactive *does not* necessarily mean unusable. E.g., in X11, + * an output can be enabled/disabled by an application by connecting/disconnecting + * a crtc + */ + const DisplayMode *currentMode() const + { + return m_bCurModeActive ? &m_CurMode : nullptr; + } + + const RectI ¤tBounds() const + { + return m_rectBounds; + } + + bool isVirtual() const + { + return m_bIsVirtual; + } + + /** + * @brief Determine if one DisplaySpec compares less than the other. + * + * Used to enforce a consistent ordering of displays, e.g. for consistent option + * presentation. Also allows DisplaySpec to be placed in a std::set + * + * @param other the other DisplaySpec to check. + * @return true if this DisplaySpec is less than the other, or false otherwise. */ + bool operator<( const DisplaySpec &other ) const + { + return m_sId < other.id(); + } + + // Lua + void PushSelf( lua_State *L ); +private: + // Unique identifier of the display + std::string m_sId; + // "Human-readable" display name + std::string m_sName; + // Modes supported by this display + std::set m_sModes; + // currently configured mode, if available + bool m_bCurModeActive; + DisplayMode m_CurMode; + // The current bounds of this display in global display coordinate space + RectI m_rectBounds; + // Flag is "true" when display represents a logical display like an X screen + // or the Win32 "Virtual screen" + bool m_bIsVirtual; +}; +/** @brief The collection of DisplaySpec available within the program. */ +typedef std::set DisplaySpecs; +//Lua +DisplaySpecs *pushDisplaySpecs(lua_State *L, const DisplaySpecs &specs); + + +#endif + +/** + * @file + * @author Chris Danford (c) 2001-2005 + * @section LICENSE + * All rights reserved. + * + * 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, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * 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 OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index fd2b1bae11..9b3575eba0 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -82,9 +82,11 @@ namespace LuaHelpers { template<> void Push( lua_State *L, const bool &Object ) { lua_pushboolean( L, Object ); } template<> void Push( lua_State *L, const float &Object ) { lua_pushnumber( L, Object ); } + template<> void Push( lua_State *L, const double &Object ) { lua_pushnumber( L, Object ); } template<> void Push( lua_State *L, const int &Object ) { lua_pushinteger( L, Object ); } template<> void Push( lua_State *L, const unsigned int &Object ) { lua_pushnumber( L, double(Object) ); } template<> void Push( lua_State *L, const RString &Object ) { lua_pushlstring( L, Object.data(), Object.size() ); } + template<> void Push( lua_State *L, std::string const& object ) { lua_pushlstring( L, object.data(), object.size() ); } template<> bool FromStack( Lua *L, bool &Object, int iOffset ) { Object = !!lua_toboolean( L, iOffset ); return true; } template<> bool FromStack( Lua *L, float &Object, int iOffset ) { Object = (float)lua_tonumber( L, iOffset ); return true; } diff --git a/src/Makefile.am b/src/Makefile.am index 949b41d0db..9a2d5cc491 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -145,7 +145,7 @@ CreateZip.cpp CreateZip.h \ CryptHelpers.cpp CryptHelpers.h \ DateTime.cpp DateTime.h \ Difficulty.cpp Difficulty.h \ -DisplayResolutions.h \ +DisplaySpec.h \ EnumHelper.cpp EnumHelper.h \ FileDownload.cpp FileDownload.h \ Font.cpp Font.h \ diff --git a/src/OptionRow.cpp b/src/OptionRow.cpp index 7f4d424630..189a665b31 100644 --- a/src/OptionRow.cpp +++ b/src/OptionRow.cpp @@ -845,10 +845,10 @@ void OptionRow::Reload() switch( m_pHand->Reload() ) { - case OptionRowHandler::RELOAD_CHANGED_NONE: + case RELOAD_CHANGED_NONE: break; - case OptionRowHandler::RELOAD_CHANGED_ALL: + case RELOAD_CHANGED_ALL: { ChoicesChanged( m_RowType ); @@ -861,7 +861,7 @@ void OptionRow::Reload() // fall through } - case OptionRowHandler::RELOAD_CHANGED_ENABLED: + case RELOAD_CHANGED_ENABLED: UpdateEnabledDisabled(); FOREACH_HumanPlayer( pn ) PositionUnderlines( pn ); diff --git a/src/OptionRowHandler.cpp b/src/OptionRowHandler.cpp index 4820b150b8..cb92d6a4cc 100644 --- a/src/OptionRowHandler.cpp +++ b/src/OptionRowHandler.cpp @@ -834,6 +834,7 @@ class OptionRowHandlerLua : public OptionRowHandler public: LuaReference *m_pLuaTable; LuaReference m_EnabledForPlayersFunc; + LuaReference m_ReloadFunc; bool m_TableIsSane; bool m_GoToFirstOnStart; @@ -941,6 +942,17 @@ public: } lua_pop(L, 1); + lua_getfield(L, -1, "Reload"); + if(!lua_isnil(L, -1)) + { + if(!lua_isfunction(L, -1)) + { + LuaHelpers::ReportScriptErrorFmt("LUA_ERROR: \"%s\" \"Reload\" entry is not a function.", RowName.c_str()); + return false; + } + } + lua_pop(L, 1); + lua_getfield(L, -1, "LoadSelections"); if(!lua_isfunction(L, -1)) { @@ -1007,6 +1019,22 @@ public: LUA->Release(L); } + void LoadChoices( Lua *L ) + { + // Iterate over the "Choices" table. + lua_getfield(L, -1, "Choices"); + lua_pushnil( L ); + while( lua_next(L, -2) != 0 ) + { + // `key' is at index -2 and `value' at index -1 + const char *pValue = lua_tostring( L, -1 ); + //LOG->Trace( "choice: '%s'", pValue); + m_Def.m_vsChoices.push_back( pValue ); + lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration + } + lua_pop( L, 1 ); // pop choices table + } + virtual bool LoadInternal( const Commands &cmds ) { const Command &command = cmds.v[0]; @@ -1059,18 +1087,7 @@ public: m_Def.m_selectType = StringToSelectType( pStr ); lua_pop( L, 1 ); - // Iterate over the "Choices" table. - lua_getfield(L, -1, "Choices"); - lua_pushnil( L ); - while( lua_next(L, -2) != 0 ) - { - // `key' is at index -2 and `value' at index -1 - const char *pValue = lua_tostring( L, -1 ); - //LOG->Trace( "choice: '%s'", pValue); - m_Def.m_vsChoices.push_back( pValue ); - lua_pop( L, 1 ); // removes `value'; keeps `key' for next iteration - } - lua_pop( L, 1 ); // pop choices table + LoadChoices( L ); // Set the EnabledForPlayers function. lua_getfield(L, -1, "EnabledForPlayers"); @@ -1093,6 +1110,10 @@ public: } lua_pop( L, 1 ); // pop ReloadRowMessages table + // Set the Reload function + lua_getfield(L, -1, "Reload"); + m_ReloadFunc.SetFromStack( L ); + lua_pop( L, 1 ); // pop main table ASSERT( lua_gettop(L) == 0 ); @@ -1102,8 +1123,47 @@ public: virtual ReloadChanged Reload() { + if (!m_TableIsSane) + { + return RELOAD_CHANGED_NONE; + } + + /* We'll always call SetEnabledForPlayers, and + * return at least RELOAD_CHANGED_ENABLED, + * to preserve original OptionRowHandlerLua behavior. + * + * Will also call the standard OptionRowHandler::Reload + * function to determine whether we should declare a full + * RELOAD_CHANGED_ALL + */ + ReloadChanged effect = RELOAD_CHANGED_ENABLED; + + if (!m_ReloadFunc.IsNil()) + { + Lua *L = LUA->Get(); + m_ReloadFunc.PushSelf( L ); + + // Argument 1: (self) + m_pLuaTable->PushSelf( L ); + RString error = "Reload: "; + + LuaHelpers::RunScriptOnStack( L, error, 1, 1, true ); + effect = std::max( effect, Enum::Check( L, -1 )); + lua_pop( L, 1 ); + + if (effect == RELOAD_CHANGED_ALL) + { + m_Def.m_vsChoices.clear(); + m_pLuaTable->PushSelf( L ); + LoadChoices( L ); + lua_pop( L, 1 ); + ASSERT( lua_gettop(L) == 0 ); + } + LUA->Release( L ); + } + SetEnabledForPlayers(); - return RELOAD_CHANGED_ENABLED; + return effect; } virtual void ImportOption( OptionRow *pRow, const vector &vpns, vector vbSelectedOut[NUM_PLAYERS] ) const @@ -1174,6 +1234,7 @@ public: ASSERT( lua_gettop(L) == 0 ); + int effects = 0; FOREACH_CONST( PlayerNumber, vpns, pn ) { PlayerNumber p = *pn; @@ -1206,9 +1267,14 @@ public: ASSERT( lua_gettop(L) == 6 ); // vbSelectedOut, m_iLuaTable, function, self, arg, arg RString error= "SaveSelections: "; - LuaHelpers::RunScriptOnStack( L, error, 3, 0, true ); - ASSERT( lua_gettop(L) == 2 ); + LuaHelpers::RunScriptOnStack( L, error, 3, 1, true ); + ASSERT( lua_gettop(L) == 3 ); // SaveSelections *may* return effects flags, otherwise nil + double ret = lua_tonumber( L, -1 ); + ASSERT_M( (lua_isnumber( L, -1 ) && std::floor( ret ) == ret) || lua_isnil( L, -1 ), + "SaveSelections must return integer flags, or nill" ); + effects |= static_cast( ret ); + lua_pop( L, 1 ); // pop effects lua_pop( L, 1 ); // pop option table lua_pop( L, 1 ); // pop vbSelected table @@ -1217,8 +1283,7 @@ public: LUA->Release(L); - // XXX: allow specifying the mask - return 0; + return effects; } virtual bool NotifyOfSelection(PlayerNumber pn, int choice) { @@ -1593,6 +1658,17 @@ OptionRowHandler* OptionRowHandlerUtil::MakeSimple( const MenuRowDef &mr ) return pHand; } +// Expose ReloadChanged to Lua +static const char *ReloadChangedNames[] = + { + "None", + "Enabled", + "All" + }; +XToString( ReloadChanged ); +StringToX( ReloadChanged ); +LuaXType( ReloadChanged ); + /* * (c) 2002-2004 Chris Danford * All rights reserved. diff --git a/src/OptionRowHandler.h b/src/OptionRowHandler.h index 5fc8a937db..d115ca8676 100644 --- a/src/OptionRowHandler.h +++ b/src/OptionRowHandler.h @@ -31,6 +31,13 @@ enum LayoutType const RString& LayoutTypeToString( LayoutType pm ); LayoutType StringToLayoutType( const RString& s ); LuaDeclareType( LayoutType ); +enum ReloadChanged +{ + RELOAD_CHANGED_NONE, RELOAD_CHANGED_ENABLED, RELOAD_CHANGED_ALL, NUM_ReloadChanged, ReloadChanged_Invalid +}; +const RString& ReloadChangedToString( ReloadChanged rc ); +ReloadChanged StringToReloadChanged( const std::string& rc ); +LuaDeclareType( ReloadChanged ); /** @brief Define the purpose of the OptionRow. */ struct OptionRowDefinition @@ -169,7 +176,6 @@ public: * graphic elements will also be reinitialized. If only m_vEnabledForPlayers * has been changed, return RELOAD_CHANGED_ENABLED. If the row is static, and * nothing has changed, return RELOAD_CHANGED_NONE. */ - enum ReloadChanged { RELOAD_CHANGED_NONE, RELOAD_CHANGED_ENABLED, RELOAD_CHANGED_ALL }; virtual ReloadChanged Reload() { return RELOAD_CHANGED_NONE; } virtual int GetDefaultOption() const { return -1; } diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index f117d9c9f6..3b8c22e0f8 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -166,6 +166,7 @@ PrefsManager::PrefsManager() : m_sDefaultModifiers ( "DefaultModifiers", "" ), m_bWindowed ( "Windowed", true ), + m_sDisplayId ( "DisplayId", "" ), m_iDisplayWidth ( "DisplayWidth", 854 ), m_iDisplayHeight ( "DisplayHeight", 480 ), m_fDisplayAspectRatio ( "DisplayAspectRatio", 16/9.f, ValidateDisplayAspectRatio ), @@ -178,6 +179,7 @@ PrefsManager::PrefsManager() : m_iMaxTextureResolution ( "MaxTextureResolution", 2048 ), m_iRefreshRate ( "RefreshRate", REFRESH_DEFAULT ), m_bAllowMultitexture ( "AllowMultitexture", true ), + m_bFullscreenIsBorderlessWindow( "FullscreenIsBorderlessWindow", false ), m_bShowStats ( "ShowStats", TRUE_IF_DEBUG), m_bShowBanners ( "ShowBanners", true ), m_bShowMouseCursor ( "ShowMouseCursor", true ), @@ -379,7 +381,7 @@ void PrefsManager::StoreGamePrefs() ASSERT( !m_sCurrentGame.Get().empty() ); // save off old values - GamePrefs &gp = m_mapGameNameToGamePrefs[m_sCurrentGame]; + GamePrefs &gp = m_mapGameNameToGamePrefs[m_sCurrentGame.Get()]; gp.m_sAnnouncer = m_sAnnouncer; gp.m_sTheme = m_sTheme; gp.m_sDefaultModifiers = m_sDefaultModifiers; diff --git a/src/PrefsManager.h b/src/PrefsManager.h index 13d8beeb04..a8f770f1ae 100644 --- a/src/PrefsManager.h +++ b/src/PrefsManager.h @@ -158,6 +158,7 @@ protected: public: Preference m_bWindowed; + Preference m_sDisplayId; Preference m_iDisplayWidth; Preference m_iDisplayHeight; Preference m_fDisplayAspectRatio; @@ -170,6 +171,7 @@ public: Preference m_iMaxTextureResolution; Preference m_iRefreshRate; Preference m_bAllowMultitexture; + Preference m_bFullscreenIsBorderlessWindow; Preference m_bShowStats; Preference m_bShowBanners; Preference m_bShowMouseCursor; diff --git a/src/ProfileManager.cpp b/src/ProfileManager.cpp index 331a52bc38..9a353f8764 100644 --- a/src/ProfileManager.cpp +++ b/src/ProfileManager.cpp @@ -230,7 +230,7 @@ bool ProfileManager::LoadLocalProfileFromMachine( PlayerNumber pn ) void ProfileManager::GetMemoryCardProfileDirectoriesToTry( vector &asDirsToTry ) { /* Try to load the preferred profile. */ - asDirsToTry.push_back( PREFSMAN->m_sMemoryCardProfileSubdir ); + asDirsToTry.push_back( PREFSMAN->m_sMemoryCardProfileSubdir.Get() ); /* If that failed, try loading from all fallback directories. */ split( g_sMemoryCardProfileImportSubdirs, ";", asDirsToTry, true ); diff --git a/src/RageDisplay.cpp b/src/RageDisplay.cpp index 35f083b416..e9b3838592 100644 --- a/src/RageDisplay.cpp +++ b/src/RageDisplay.cpp @@ -12,7 +12,7 @@ #include "RageSurface.h" #include "Preference.h" #include "LocalizedString.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "arch/ArchHooks/ArchHooks.h" // Statistics stuff @@ -90,17 +90,33 @@ RString RageDisplay::SetVideoMode( VideoModeParams p, bool &bNeedReloadTextures vs.push_back( err ); // Fall back on a known resolution good rather than 640 x 480. - DisplayResolutions dr; - this->GetDisplayResolutions( dr ); + DisplaySpecs dr; + this->GetDisplaySpecs(dr); if( dr.empty() ) { vs.push_back( "No display resolutions" ); return SETVIDEOMODE_FAILED.GetValue() + " " + join(";",vs); } - const DisplayResolution &d = *dr.begin(); - p.width = d.iWidth; - p.height = d.iHeight; + DisplaySpec d = *dr.begin(); + // Try to find DisplaySpec corresponding to requested display + for (const auto &candidate: dr) + { + if (candidate.currentMode() != nullptr) + { + d = candidate; + if (candidate.id() == p.sDisplayId) + { + break; + } + } + } + + p.sDisplayId = d.id(); + const DisplayMode supported = d.currentMode() != nullptr ? *d.currentMode() : *d.supportedModes().begin(); + p.width = supported.width; + p.height = supported.height; + p.rate = static_cast (round(supported.refreshRate)); if( (err = this->TryVideoMode(p,bNeedReloadTextures)) == "" ) return RString(); vs.push_back( err ); @@ -714,8 +730,8 @@ void RageDisplay::ChangeCentering( int iTranslateX, int iTranslateY, int iAddWid RageMatrix RageDisplay::GetCenteringMatrix( float fTranslateX, float fTranslateY, float fAddWidth, float fAddHeight ) const { // in screen space, left edge = -1, right edge = 1, bottom edge = -1. top edge = 1 - float fWidth = (float) GetActualVideoModeParams().width; - float fHeight = (float) GetActualVideoModeParams().height; + float fWidth = (float) GetActualVideoModeParams().windowWidth; + float fHeight = (float) GetActualVideoModeParams().windowHeight; float fPercentShiftX = SCALE( fTranslateX, 0, fWidth, 0, +2.0f ); float fPercentShiftY = SCALE( fTranslateY, 0, fHeight, 0, -2.0f ); float fPercentScaleX = SCALE( fAddWidth, 0, fWidth, 1.0f, 2.0f ); @@ -990,6 +1006,16 @@ void RageCompiledGeometry::Set( const vector &vMeshes, bool bNeedsNormal // lua start #include "LuaBinding.h" +// Register with Lua. +static void register_REFRESH_DEFAULT(lua_State *L) +{ + lua_pushstring( L, "REFRESH_DEFAULT" ); + lua_pushinteger( L, REFRESH_DEFAULT ); + lua_settable( L, LUA_GLOBALSINDEX); +} +REGISTER_WITH_LUA_FUNCTION( register_REFRESH_DEFAULT ); + + /** @brief Allow Lua to have access to the RageDisplay. */ class LunaRageDisplay: public Luna { @@ -1026,6 +1052,26 @@ public: return 1; } + static int GetDisplaySpecs( T* p, lua_State *L ) + { + DisplaySpecs s; + p->GetDisplaySpecs(s); + pushDisplaySpecs(L, s); + return 1; + } + + static int SupportsRenderToTexture( T* p, lua_State *L ) + { + lua_pushboolean(L, p->SupportsRenderToTexture()); + return 1; + } + + static int SupportsFullscreenBorderlessWindow( T* p, lua_State *L ) + { + lua_pushboolean(L, p->SupportsFullscreenBorderlessWindow()); + return 1; + } + LunaRageDisplay() { ADD_METHOD( GetDisplayWidth ); @@ -1033,6 +1079,9 @@ public: ADD_METHOD( GetFPS ); ADD_METHOD( GetVPF ); ADD_METHOD( GetCumFPS ); + ADD_METHOD( GetDisplaySpecs ); + ADD_METHOD( SupportsRenderToTexture ); + ADD_METHOD( SupportsFullscreenBorderlessWindow); } }; diff --git a/src/RageDisplay.h b/src/RageDisplay.h index 5f286681d6..35eb3c139c 100644 --- a/src/RageDisplay.h +++ b/src/RageDisplay.h @@ -7,8 +7,8 @@ #include "ModelTypes.h" #include -class DisplayResolution; -typedef set DisplayResolutions; +class DisplaySpec; +typedef std::set DisplaySpecs; const int REFRESH_DEFAULT = 0; struct RageSurface; @@ -79,6 +79,7 @@ public: // are filled (in case new params are added). VideoModeParams( bool windowed_, + RString sDisplayId_, int width_, int height_, int bpp_, @@ -88,12 +89,14 @@ public: bool bSmoothLines_, bool bTrilinearFiltering_, bool bAnisotropicFiltering_, + bool bWindowIsFullscreenBorderless_, RString sWindowTitle_, RString sIconFile_, bool PAL_, float fDisplayAspectRatio_ ): windowed(windowed_), + sDisplayId(sDisplayId_), width(width_), height(height_), bpp(bpp_), @@ -103,18 +106,36 @@ public: bSmoothLines(bSmoothLines_), bTrilinearFiltering(bTrilinearFiltering_), bAnisotropicFiltering(bAnisotropicFiltering_), + bWindowIsFullscreenBorderless(bWindowIsFullscreenBorderless_), sWindowTitle(sWindowTitle_), sIconFile(sIconFile_), PAL(PAL_), fDisplayAspectRatio(fDisplayAspectRatio_) {} + VideoModeParams(const VideoModeParams &other): + windowed(other.windowed), sDisplayId(other.sDisplayId), + width(other.width), height(other.height), + bpp(other.bpp), rate(other.rate), + vsync(other.vsync), interlaced(other.interlaced), + bSmoothLines(other.bSmoothLines), bTrilinearFiltering(other.bTrilinearFiltering), + bAnisotropicFiltering(other.bAnisotropicFiltering), bWindowIsFullscreenBorderless(other.bWindowIsFullscreenBorderless), + sWindowTitle(other.sWindowTitle), sIconFile(other.sIconFile), + PAL(other.PAL), fDisplayAspectRatio(other.fDisplayAspectRatio) + {} + VideoModeParams(): windowed(false), width(0), height(0), - bpp(0), rate(0), vsync(false), interlaced(false), - bSmoothLines(false), bTrilinearFiltering(false), - bAnisotropicFiltering(false), sWindowTitle(RString()), - sIconFile(RString()), PAL(false), fDisplayAspectRatio(0.0f) {} + bpp(0), rate(0), vsync(false), interlaced(false), + bSmoothLines(false), bTrilinearFiltering(false), + bAnisotropicFiltering(false), bWindowIsFullscreenBorderless(false), + sWindowTitle(RString()), sIconFile(RString()), + PAL(false), fDisplayAspectRatio(0.0f) {} + + // Subclassing VideoModeParams in ActualVideoModeParams. Make destructor virtual just in case + // someone tries to delete one of those through a pointer to base... + virtual ~VideoModeParams() {} bool windowed; + RString sDisplayId; int width; int height; int bpp; @@ -124,12 +145,41 @@ public: bool bSmoothLines; bool bTrilinearFiltering; bool bAnisotropicFiltering; + bool bWindowIsFullscreenBorderless; RString sWindowTitle; RString sIconFile; bool PAL; float fDisplayAspectRatio; }; +/** + * @brief The _actual_ VideoModeParams determined by the LowLevelWindow implementation. + * Contains all the attributes of VideoModeParams, plus the actual window width/height determined by + * LLW + */ +class ActualVideoModeParams: public VideoModeParams +{ +public: + ActualVideoModeParams(): VideoModeParams(), windowWidth(0), windowHeight(0), renderOffscreen(false) {} + ActualVideoModeParams( const VideoModeParams ¶ms ) : VideoModeParams( params ), + windowWidth( params.width ), + windowHeight( params.height ), + renderOffscreen( false ) + { } + ActualVideoModeParams( const VideoModeParams ¶ms, int windowWidth, int windowHeight, bool renderOffscreen ) : + VideoModeParams( params ), windowWidth( windowWidth ), windowHeight( windowHeight ), + renderOffscreen( renderOffscreen ) + { } + ActualVideoModeParams (const ActualVideoModeParams &other) = default; + + // If bWindowIsFullscreenBorderless is true, + // then these properties will differ from width/height (which describe the + // render size) + int windowWidth; + int windowHeight; + bool renderOffscreen; +}; + struct RenderTargetParam { RenderTargetParam(): @@ -181,7 +231,7 @@ public: virtual RString Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ) = 0; virtual RString GetApiDescription() const = 0; - virtual void GetDisplayResolutions( DisplayResolutions &out ) const = 0; + virtual void GetDisplaySpecs(DisplaySpecs &out) const = 0; // Don't override this. Override TryVideoMode() instead. // This will set the video mode to be as close as possible to params. @@ -193,7 +243,7 @@ public: virtual bool BeginFrame(); virtual void EndFrame(); - virtual VideoModeParams GetActualVideoModeParams() const = 0; + virtual ActualVideoModeParams GetActualVideoModeParams() const = 0; bool IsWindowed() const { return this->GetActualVideoModeParams().windowed; } virtual void SetBlendMode( BlendMode mode ) = 0; @@ -235,7 +285,8 @@ public: virtual void SetEffectMode( EffectMode ) { } virtual bool IsEffectModeSupported( EffectMode effect ) { return effect == EffectMode_Normal; } - bool SupportsRenderToTexture() const { return false; } + virtual bool SupportsRenderToTexture() const { return false; } + virtual bool SupportsFullscreenBorderlessWindow() const { return false; } /* Create a render target, returning a texture handle. In addition to normal * texture functions, this can be passed to SetRenderTarget. Delete with diff --git a/src/RageDisplay_D3D.cpp b/src/RageDisplay_D3D.cpp index 7e9413bc21..960c1755aa 100644 --- a/src/RageDisplay_D3D.cpp +++ b/src/RageDisplay_D3D.cpp @@ -10,7 +10,7 @@ #include "RageSurface.h" #include "RageSurfaceUtils.h" #include "EnumHelper.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "LocalizedString.h" #include @@ -271,19 +271,39 @@ RageDisplay_D3D::~RageDisplay_D3D() } } -void RageDisplay_D3D::GetDisplayResolutions( DisplayResolutions &out ) const +void RageDisplay_D3D::GetDisplaySpecs( DisplaySpecs &out ) const { out.clear(); int iCnt = g_pd3d->GetAdapterModeCount( D3DADAPTER_DEFAULT, g_DefaultAdapterFormat ); - for( int i = 0; i < iCnt; ++i ) + std::set modes; + D3DDISPLAYMODE mode; + for ( int i = 0; i < iCnt; ++i ) { - D3DDISPLAYMODE mode; g_pd3d->EnumAdapterModes( D3DADAPTER_DEFAULT, g_DefaultAdapterFormat, i, &mode ); - - DisplayResolution res = { mode.Width, mode.Height }; - out.insert( res ); + modes.insert( { mode.Width, mode.Height, static_cast (mode.RefreshRate) } ); } + + // Get the current display mode + if ( g_pd3d->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &mode ) == D3D_OK ) + { + D3DADAPTER_IDENTIFIER9 ID; + g_pd3d->GetAdapterIdentifier( D3DADAPTER_DEFAULT, 0, &ID ); + DisplayMode active = { mode.Width, mode.Height, static_cast (mode.RefreshRate) }; + RectI bounds( 0, 0, active.width, active.height ); + out.insert( DisplaySpec( "", "Fullscreen", modes, active, bounds ) ); + } + else + { + LOG->Warn( "Could not find active mode for default D3D adapter" ); + if ( !modes.empty() ) + { + const DisplayMode &m = *modes.begin(); + RectI bounds( 0, 0, m.width, m.height ); + out.insert( DisplaySpec( "", "Fullscreen", modes, m, bounds ) ); + } + } + } D3DFORMAT FindBackBufferType(bool bWindowed, int iBPP) @@ -677,10 +697,9 @@ RageSurface* RageDisplay_D3D::CreateScreenshot() return result; } -VideoModeParams RageDisplay_D3D::GetActualVideoModeParams() const +ActualVideoModeParams RageDisplay_D3D::GetActualVideoModeParams() const { - VideoModeParams p = GraphicsWindow::GetParams(); - return p; + return GraphicsWindow::GetParams(); } void RageDisplay_D3D::SendCurrentMatrices() diff --git a/src/RageDisplay_D3D.h b/src/RageDisplay_D3D.h index 3064273b39..6821fcfd8a 100644 --- a/src/RageDisplay_D3D.h +++ b/src/RageDisplay_D3D.h @@ -11,13 +11,13 @@ public: virtual RString Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ); virtual RString GetApiDescription() const { return "D3D"; } - virtual void GetDisplayResolutions( DisplayResolutions &out ) const; + virtual void GetDisplaySpecs( DisplaySpecs &out ) const; void ResolutionChanged(); const RagePixelFormatDesc *GetPixelFormatDesc(RagePixelFormat pf) const; bool BeginFrame(); void EndFrame(); - VideoModeParams GetActualVideoModeParams() const; + ActualVideoModeParams GetActualVideoModeParams() const; void SetBlendMode( BlendMode mode ); bool SupportsTextureFormat( RagePixelFormat pixfmt, bool realtime=false ); bool SupportsThreadedRendering(); diff --git a/src/RageDisplay_GLES2.cpp b/src/RageDisplay_GLES2.cpp index adad7fa4e2..45062ff7b6 100644 --- a/src/RageDisplay_GLES2.cpp +++ b/src/RageDisplay_GLES2.cpp @@ -11,7 +11,7 @@ #include "RageSurface.h" #include "RageTextureManager.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "arch/LowLevelWindow/LowLevelWindow.h" @@ -444,10 +444,10 @@ RageDisplay_GLES2::~RageDisplay_GLES2() } void -RageDisplay_GLES2::GetDisplayResolutions( DisplayResolutions &out ) const +RageDisplay_GLES2::GetDisplaySpecs(DisplaySpecs &out) const { out.clear(); - g_pWind->GetDisplayResolutions( out ); + g_pWind->GetDisplaySpecs(out); } RageSurface* @@ -517,7 +517,7 @@ RageDisplay_GLES2::GetApiDescription() const return "OpenGL ES 2.0"; } -VideoModeParams +ActualVideoModeParams RageDisplay_GLES2::GetActualVideoModeParams() const { return g_pWind->GetActualVideoModeParams(); diff --git a/src/RageDisplay_GLES2.h b/src/RageDisplay_GLES2.h index a44c3625cf..36b52408bd 100644 --- a/src/RageDisplay_GLES2.h +++ b/src/RageDisplay_GLES2.h @@ -9,21 +9,21 @@ public: virtual RString Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ); virtual RString GetApiDescription() const; - virtual void GetDisplayResolutions( DisplayResolutions &out ) const; + virtual void GetDisplaySpecs(DisplaySpecs &out) const; const RagePixelFormatDesc *GetPixelFormatDesc(RagePixelFormat pf) const; bool BeginFrame(); void EndFrame(); - VideoModeParams GetActualVideoModeParams() const; + ActualVideoModeParams GetActualVideoModeParams() const; void SetBlendMode( BlendMode mode ); bool SupportsTextureFormat( RagePixelFormat pixfmt, bool realtime=false ); bool SupportsPerVertexMatrixScale(); - unsigned CreateTexture( - RagePixelFormat pixfmt, + unsigned CreateTexture( + RagePixelFormat pixfmt, RageSurface* img, bool bGenerateMipMaps ); - void UpdateTexture( - unsigned iTexHandle, + void UpdateTexture( + unsigned iTexHandle, RageSurface* img, int xoffset, int yoffset, int width, int height ); void DeleteTexture( unsigned iTexHandle ); @@ -42,7 +42,7 @@ public: void ClearZBuffer(); void SetCullMode( CullMode mode ); void SetAlphaTest( bool b ); - void SetMaterial( + void SetMaterial( const RageColor &emissive, const RageColor &ambient, const RageColor &diffuse, diff --git a/src/RageDisplay_Null.cpp b/src/RageDisplay_Null.cpp index 16d8fd7607..da0dfe3c8a 100644 --- a/src/RageDisplay_Null.cpp +++ b/src/RageDisplay_Null.cpp @@ -9,7 +9,7 @@ #include "RageTypes.h" #include "RageUtil.h" #include "RageSurface.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" static RageDisplay::RagePixelFormatDesc PIXEL_FORMAT_DESC[NUM_RagePixelFormat] = { { @@ -81,11 +81,12 @@ RString RageDisplay_Null::Init( const VideoModeParams &p, bool /* bAllowUnaccele return RString(); } -void RageDisplay_Null::GetDisplayResolutions( DisplayResolutions &out ) const +void RageDisplay_Null::GetDisplaySpecs(DisplaySpecs &out) const { out.clear(); - DisplayResolution res = { 640, 480, true }; - out.insert( res ); + DisplayMode nullMode = {640U, 480U, 30.0}; + DisplaySpec nullSpec("NullDisplay", "NullDisplay", nullMode); + out.insert( nullSpec ); } RageSurface* RageDisplay_Null::CreateScreenshot() diff --git a/src/RageDisplay_Null.h b/src/RageDisplay_Null.h index 88cb6d64b9..e08548e71c 100644 --- a/src/RageDisplay_Null.h +++ b/src/RageDisplay_Null.h @@ -10,23 +10,23 @@ public: virtual RString Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ); virtual RString GetApiDescription() const { return "Null"; } - virtual void GetDisplayResolutions( DisplayResolutions &out ) const; + virtual void GetDisplaySpecs(DisplaySpecs &out) const; const RagePixelFormatDesc *GetPixelFormatDesc(RagePixelFormat pf) const; bool BeginFrame() { return true; } void EndFrame(); - VideoModeParams GetActualVideoModeParams() const { return m_Params; } + ActualVideoModeParams GetActualVideoModeParams() const { return m_Params; } void SetBlendMode( BlendMode ) { } bool SupportsTextureFormat( RagePixelFormat, bool /* realtime */ =false ) { return true; } bool SupportsPerVertexMatrixScale() { return false; } - unsigned CreateTexture( - RagePixelFormat, + unsigned CreateTexture( + RagePixelFormat, RageSurface* /* img */, bool /* bGenerateMipMaps */ ) { return 1; } - void UpdateTexture( - unsigned /* iTexHandle */, + void UpdateTexture( + unsigned /* iTexHandle */, RageSurface* /* img */, - int /* xoffset */, int /* yoffset */, int /* width */, int /* height */ + int /* xoffset */, int /* yoffset */, int /* width */, int /* height */ ) { } void DeleteTexture( unsigned /* iTexHandle */ ) { } void ClearAllTextures() { } @@ -44,7 +44,7 @@ public: void ClearZBuffer() { } void SetCullMode( CullMode ) { } void SetAlphaTest( bool ) { } - void SetMaterial( + void SetMaterial( const RageColor & /* unreferenced: emissive */, const RageColor & /* unreferenced: ambient */, const RageColor & /* unreferenced: diffuse */, diff --git a/src/RageDisplay_OGL.cpp b/src/RageDisplay_OGL.cpp index 3e6219748f..cdfffd846d 100644 --- a/src/RageDisplay_OGL.cpp +++ b/src/RageDisplay_OGL.cpp @@ -15,7 +15,7 @@ using namespace RageDisplay_Legacy_Helpers; #include "RageUtil.h" #include "EnumHelper.h" #include "Foreach.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "LocalizedString.h" #include "arch/LowLevelWindow/LowLevelWindow.h" @@ -63,7 +63,7 @@ static const GLenum RageSpriteVertexFormat = GL_T2F_C4F_N3F_V3F; /* If we support texture matrix scaling, a handle to the vertex program: */ static GLhandleARB g_bTextureMatrixShader = 0; -static map g_mapRenderTargets; +static std::map g_mapRenderTargets; static RenderTarget *g_pCurrentRenderTarget = NULL; static LowLevelWindow *g_pWind; @@ -264,6 +264,7 @@ RageDisplay_Legacy::RageDisplay_Legacy() g_pWind = NULL; g_bTextureMatrixShader = 0; + offscreenRenderTarget = nullptr; } RString GetInfoLog( GLhandleARB h ) @@ -561,10 +562,10 @@ RageDisplay_Legacy::~RageDisplay_Legacy() delete g_pWind; } -void RageDisplay_Legacy::GetDisplayResolutions( DisplayResolutions &out ) const +void RageDisplay_Legacy::GetDisplaySpecs(DisplaySpecs &out) const { out.clear(); - g_pWind->GetDisplayResolutions( out ); + g_pWind->GetDisplaySpecs(out); } static void CheckPalettedTextures() @@ -685,7 +686,9 @@ void SetupExtensions() const float fGLUVersion = StringToFloat( (const char *) gluGetString(GLU_VERSION) ); g_gluVersion = lrintf( fGLUVersion * 10 ); +#ifndef HAVE_X11 // LLW_X11 needs to init GLEW early for GLX exts glewInit(); +#endif g_iMaxTextureUnits = 1; if (GLEW_ARB_multitexture) @@ -712,6 +715,39 @@ void SetupExtensions() } } +bool RageDisplay_Legacy::UseOffscreenRenderTarget() +{ + if ( !GetActualVideoModeParams().renderOffscreen || !TEXTUREMAN ) + { + return false; + } + + if ( !offscreenRenderTarget ) + { + RenderTargetParam param; + param.bWithDepthBuffer = true; + param.bWithAlpha = true; + param.bFloat = false; + param.iWidth = GetActualVideoModeParams().width; + param.iHeight = GetActualVideoModeParams().height; + RageTextureID id( ssprintf( "FullscreenTexture%dx%d", param.iWidth, + param.iHeight ) ); + // See if we have this texture loaded already + // (not GC'd yet). If it exists and we try to recreate + // it, we'll get an error + if ( TEXTUREMAN->IsTextureRegistered( id ) ) + { + offscreenRenderTarget = static_cast( TEXTUREMAN->LoadTexture( id ) ); + } + else + { + offscreenRenderTarget = new RageTextureRenderTarget( id, param ); + TEXTUREMAN->RegisterTexture( id, offscreenRenderTarget ); + } + } + return true; +} + void RageDisplay_Legacy::ResolutionChanged() { //LOG->Warn( "RageDisplay_Legacy::ResolutionChanged" ); @@ -721,6 +757,13 @@ void RageDisplay_Legacy::ResolutionChanged() EndFrame(); RageDisplay::ResolutionChanged(); + + if (offscreenRenderTarget && TEXTUREMAN) + { + TEXTUREMAN->UnloadTexture( offscreenRenderTarget ); + offscreenRenderTarget = NULL; + } + } // Return true if mode change was successful. @@ -746,6 +789,7 @@ RString RageDisplay_Legacy::TryVideoMode( const VideoModeParams &p, bool &bNewDe if (TEXTUREMAN) TEXTUREMAN->InvalidateTextures(); + /* Delete all render targets. They may have associated resources other than * the texture itself. */ FOREACHM( unsigned, RenderTarget *, g_mapRenderTargets, rt ) @@ -784,8 +828,8 @@ bool RageDisplay_Legacy::BeginFrame() { /* We do this in here, rather than ResolutionChanged, or we won't update the * viewport for the concurrent rendering context. */ - int fWidth = g_pWind->GetActualVideoModeParams().width; - int fHeight = g_pWind->GetActualVideoModeParams().height; + int fWidth = g_pWind->GetActualVideoModeParams().windowWidth; + int fHeight = g_pWind->GetActualVideoModeParams().windowHeight; glViewport( 0, 0, fWidth, fHeight ); @@ -793,11 +837,34 @@ bool RageDisplay_Legacy::BeginFrame() SetZWrite( true ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - return RageDisplay::BeginFrame(); + bool beginFrame = RageDisplay::BeginFrame(); + if (beginFrame && UseOffscreenRenderTarget()) { + offscreenRenderTarget->BeginRenderingTo( false ); + } + + return beginFrame; } void RageDisplay_Legacy::EndFrame() { + if (UseOffscreenRenderTarget()) + { + offscreenRenderTarget->FinishRenderingTo(); + Sprite fullscreenSprite; + // We've got a hold of this, don't want sprite deleting it when + // it's deleted + offscreenRenderTarget->m_iRefCount++; + fullscreenSprite.SetTexture(offscreenRenderTarget); + fullscreenSprite.SetHorizAlign(align_left); + fullscreenSprite.SetVertAlign(align_top); + CameraPushMatrix(); + LoadMenuPerspective( 0, GetActualVideoModeParams().width, GetActualVideoModeParams().height, + static_cast (GetActualVideoModeParams().width) / 2.f, + static_cast (GetActualVideoModeParams().height) / 2.f ); + fullscreenSprite.Draw(); + CameraPopMatrix(); + } + FrameLimitBeforeVsync( g_pWind->GetActualVideoModeParams().rate ); g_pWind->SwapBuffers(); FrameLimitAfterVsync(); @@ -823,20 +890,31 @@ RageSurface* RageDisplay_Legacy::CreateScreenshot() int width = g_pWind->GetActualVideoModeParams().width; int height = g_pWind->GetActualVideoModeParams().height; - const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[RagePixelFormat_RGBA8]; - RageSurface *image = CreateSurface( width, height, desc.bpp, - desc.masks[0], desc.masks[1], desc.masks[2], 0 ); + RageSurface *image = NULL; + if (offscreenRenderTarget) { + RageSurface *raw = GetTexture(offscreenRenderTarget->GetTexHandle()); + image = CreateSurface( offscreenRenderTarget->GetImageWidth(), offscreenRenderTarget->GetImageHeight(), + raw->fmt.BitsPerPixel, raw->fmt.Rmask, raw->fmt.Gmask, raw->fmt.Bmask, + raw->fmt.Amask ); + RageSurfaceUtils::Blit(raw, image); + delete raw; + } else { + const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[RagePixelFormat_RGBA8]; + image = CreateSurface( width, height, desc.bpp, + desc.masks[0], desc.masks[1], desc.masks[2], 0 ); - DebugFlushGLErrors(); + DebugFlushGLErrors(); - glReadBuffer( GL_FRONT ); - DebugAssertNoGLError(); + //TODO: revisit for MacOS, where backbuffer size can be less than window size + glReadBuffer( GL_FRONT ); + DebugAssertNoGLError(); - glReadPixels( 0, 0, g_pWind->GetActualVideoModeParams().width, g_pWind->GetActualVideoModeParams().height, GL_RGBA, - GL_UNSIGNED_BYTE, image->pixels ); - DebugAssertNoGLError(); + glReadPixels( 0, 0, g_pWind->GetActualVideoModeParams().width, g_pWind->GetActualVideoModeParams().height, GL_RGBA, + GL_UNSIGNED_BYTE, image->pixels ); + DebugAssertNoGLError(); - RageSurfaceUtils::FlipVertically( image ); + RageSurfaceUtils::FlipVertically( image ); + } return image; } @@ -865,7 +943,7 @@ RageSurface *RageDisplay_Legacy::GetTexture( unsigned iTexture ) return pImage; } -VideoModeParams RageDisplay_Legacy::GetActualVideoModeParams() const +ActualVideoModeParams RageDisplay_Legacy::GetActualVideoModeParams() const { return g_pWind->GetActualVideoModeParams(); } @@ -2483,6 +2561,14 @@ bool RageDisplay_Legacy::SupportsRenderToTexture() const return GLEW_EXT_framebuffer_object || g_pWind->SupportsRenderToTexture(); } +bool RageDisplay_Legacy::SupportsFullscreenBorderlessWindow() const +{ + // In order to support FSBW, we're going to need the LowLevelWindow implementation + // to support creating a fullscreen borderless window, and we're going to need + // RenderToTexture support in order to render in alternative resolutions + return g_pWind->SupportsFullscreenBorderlessWindow() && SupportsRenderToTexture(); +} + /* * Render-to-texture can be implemented in several ways: the generic GL_ARB_pixel_buffer_object, * or platform-specifically. PBO is not available on all hardware that supports RTT, @@ -2525,8 +2611,8 @@ void RageDisplay_Legacy::SetRenderTarget( unsigned iTexture, bool bPreserveTextu DISPLAY->CameraPopMatrix(); /* Reset the viewport. */ - int fWidth = g_pWind->GetActualVideoModeParams().width; - int fHeight = g_pWind->GetActualVideoModeParams().height; + int fWidth = g_pWind->GetActualVideoModeParams().windowWidth; + int fHeight = g_pWind->GetActualVideoModeParams().windowHeight; glViewport( 0, 0, fWidth, fHeight ); if (g_pCurrentRenderTarget) diff --git a/src/RageDisplay_OGL.h b/src/RageDisplay_OGL.h index 8fa8f3a5a0..48b4d327d8 100644 --- a/src/RageDisplay_OGL.h +++ b/src/RageDisplay_OGL.h @@ -4,9 +4,11 @@ #define RAGE_DISPLAY_OGL_H #include "RageDisplay.h" +#include "RageTextureRenderTarget.h" +#include "Sprite.h" /* Making an OpenGL call doesn't also flush the error state; if we happen - * to have an error from a previous call, then the assert below will fail. + * to have an error from a previous call, then the assert below will fail. * Flush it. */ #define FlushGLErrors() do { } while( glGetError() != GL_NO_ERROR ) #define AssertNoGLError() \ @@ -31,7 +33,7 @@ public: virtual RString Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ); virtual RString GetApiDescription() const { return "OpenGL"; } - virtual void GetDisplayResolutions( DisplayResolutions &out ) const; + virtual void GetDisplaySpecs(DisplaySpecs &out) const; void ResolutionChanged(); const RagePixelFormatDesc *GetPixelFormatDesc(RagePixelFormat pf) const; @@ -41,22 +43,23 @@ public: void BeginConcurrentRendering(); void EndConcurrentRendering(); - bool BeginFrame(); + bool BeginFrame(); void EndFrame(); - VideoModeParams GetActualVideoModeParams() const; + ActualVideoModeParams GetActualVideoModeParams() const; void SetBlendMode( BlendMode mode ); bool SupportsTextureFormat( RagePixelFormat pixfmt, bool realtime=false ); bool SupportsPerVertexMatrixScale(); - unsigned CreateTexture( - RagePixelFormat pixfmt, + unsigned CreateTexture( + RagePixelFormat pixfmt, RageSurface* img, bool bGenerateMipMaps ); - void UpdateTexture( - unsigned iTexHandle, + void UpdateTexture( + unsigned iTexHandle, RageSurface* img, - int xoffset, int yoffset, int width, int height + int xoffset, int yoffset, int width, int height ); void DeleteTexture( unsigned iTexHandle ); + bool UseOffscreenRenderTarget(); RageSurface *GetTexture( unsigned iTexture ); RageTextureLock *CreateTextureLock(); @@ -70,6 +73,7 @@ public: void SetEffectMode( EffectMode effect ); bool IsEffectModeSupported( EffectMode effect ); bool SupportsRenderToTexture() const; + bool SupportsFullscreenBorderlessWindow() const; unsigned CreateRenderTarget( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ); unsigned GetRenderTarget(); void SetRenderTarget( unsigned iHandle, bool bPreserveTexture ); @@ -81,7 +85,7 @@ public: void ClearZBuffer(); void SetCullMode( CullMode mode ); void SetAlphaTest( bool b ); - void SetMaterial( + void SetMaterial( const RageColor &emissive, const RageColor &ambient, const RageColor &diffuse, @@ -125,6 +129,9 @@ protected: bool SupportsSurfaceFormat( RagePixelFormat pixfmt ); void SendCurrentMatrices(); + +private: + RageTextureRenderTarget *offscreenRenderTarget; }; #endif diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index 0d52c6c0f8..a840751a08 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -247,6 +247,7 @@ ScreenManager::ScreenManager() m_bZeroNextUpdate = false; m_PopTopScreen = SM_Invalid; m_OnDonePreparingScreen = SM_Invalid; + } diff --git a/src/ScreenOptionsMasterPrefs.cpp b/src/ScreenOptionsMasterPrefs.cpp index acf815d7f5..67b9030a03 100644 --- a/src/ScreenOptionsMasterPrefs.cpp +++ b/src/ScreenOptionsMasterPrefs.cpp @@ -14,7 +14,7 @@ #include "Game.h" #include "Foreach.h" #include "GameConstantsAndTypes.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "LocalizedString.h" #include "SpecialFiles.h" #include "RageLog.h" @@ -248,22 +248,25 @@ static void ThemeChoices( vector &out ) *s = THEME->GetThemeDisplayName( *s ); } -static DisplayResolutions display_resolution_list; -static void cache_display_resolution_list() +static DisplaySpecs display_specs; +static void cache_display_specs() { - if(display_resolution_list.empty()) + if(display_specs.empty()) { - DISPLAY->GetDisplayResolutions(display_resolution_list); + DISPLAY->GetDisplaySpecs(display_specs); } } static void DisplayResolutionChoices( vector &out ) { - cache_display_resolution_list(); - FOREACHS_CONST( DisplayResolution, display_resolution_list, iter ) + cache_display_specs(); + FOREACHS_CONST( DisplaySpec, display_specs, iter ) { - RString s = ssprintf("%dx%d", iter->iWidth, iter->iHeight); - out.push_back( s ); + if (iter->currentMode() != NULL) + { + RString s = ssprintf("%dx%d", iter->currentMode()->width, iter->currentMode()->height); + out.push_back(s); + } } } @@ -516,6 +519,8 @@ static void MaxHighScoresPerListForPlayer(int& sel, bool to_sel, ConfOption cons #include "LuaManager.h" +#include "LuaBinding.h" + static int GetTimingDifficulty() { int iTimingDifficulty = 0; @@ -583,12 +588,14 @@ static void DisplayResolutionM( int &sel, bool ToSel, const ConfOption *pConfOpt { static vector res_choices; - if(res_choices.empty()) + if( res_choices.empty() ) { - cache_display_resolution_list(); - FOREACHS_CONST(DisplayResolution, display_resolution_list, iter) + FOREACHS_CONST( DisplaySpec, display_specs, iter ) { - res_choices.push_back(res_t(iter->iWidth, iter->iHeight)); + if ( iter->currentMode() != NULL ) + { + res_choices.push_back( res_t( iter->currentMode()->width, iter->currentMode()->height ) ); + } } } @@ -733,10 +740,10 @@ static void InitializeConfOptions() if( !g_ConfOptions.empty() ) return; - // Clear the display_resolution_list so that we don't get problems from + // Clear the display_specs so that we don't get problems from // caching it. If the DisplayResolution option row is on the screen, it'll // recache the list. -Kyz - display_resolution_list.clear(); + display_specs.clear(); // There are a couple ways of getting the current preference column or turning // a new choice in the interface into a new preference. The easiest is when @@ -958,6 +965,19 @@ void ConfOption::MakeOptionsList( vector &out ) const out = names; } +static const char *OptEffectNames[] = { + "SavePreferences", + "ApplyGraphics", + "ApplyTheme", + "ChangeGame", + "ApplySound", + "ApplySong", + "ApplyAspectRatio" +}; +XToString( OptEffect ); +StringToX( OptEffect ); +LuaXType( OptEffect ); + /** * @file * @author Glenn Maynard (c) 2003-2004 diff --git a/src/ScreenOptionsMasterPrefs.h b/src/ScreenOptionsMasterPrefs.h index ef3eccabbf..56df43bbc4 100644 --- a/src/ScreenOptionsMasterPrefs.h +++ b/src/ScreenOptionsMasterPrefs.h @@ -1,14 +1,24 @@ #ifndef SCREEN_OPTIONS_MASTER_PREFS_H #define SCREEN_OPTIONS_MASTER_PREFS_H +#include "EnumHelper.h" + static const int MAX_OPTIONS=16; -#define OPT_SAVE_PREFERENCES (1<<0) -#define OPT_APPLY_GRAPHICS (1<<1) -#define OPT_APPLY_THEME (1<<2) -#define OPT_CHANGE_GAME (1<<3) -#define OPT_APPLY_SOUND (1<<4) -#define OPT_APPLY_SONG (1<<5) -#define OPT_APPLY_ASPECT_RATIO (1<<6) +enum OptEffect +{ + OPT_SAVE_PREFERENCES = (1<<0), + OPT_APPLY_GRAPHICS = (1<<1), + OPT_APPLY_THEME = (1<<2), + OPT_CHANGE_GAME = (1<<3), + OPT_APPLY_SOUND = (1<<4), + OPT_APPLY_SONG = (1<<5), + OPT_APPLY_ASPECT_RATIO = (1<<6), + NUM_OptEffect = 7, + OptEffect_Invalid = MAX_OPTIONS+1 +}; +const RString& OptEffectToString( OptEffect e ); +OptEffect StringToOptEffect( const std::string &e ); +LuaDeclareType( OptEffect ); struct ConfOption { diff --git a/src/Sprite.cpp b/src/Sprite.cpp index a4fe6962f5..52f385db8f 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -84,6 +84,31 @@ Sprite::Sprite( const Sprite &cpy ): m_pTexture = NULL; } +Sprite &Sprite::operator=( Sprite other ) +{ + using std::swap; +#define SWAP(a) swap(a, other.a) + SWAP( m_States ); + SWAP(m_animation_length_seconds); + SWAP( m_iCurState ); + SWAP( m_fSecsIntoState ); + SWAP( m_bUsingCustomTexCoords ); + SWAP( m_bUsingCustomPosCoords ); + SWAP( m_bSkipNextUpdate ); + SWAP( m_DecodeMovie ); + SWAP( m_EffectMode ); + memcpy( m_CustomTexCoords, other.m_CustomTexCoords, sizeof(m_CustomTexCoords) ); + memcpy( m_CustomPosCoords, other.m_CustomPosCoords, sizeof(m_CustomPosCoords) ); + SWAP( m_fRememberedClipWidth ); + SWAP( m_fRememberedClipHeight ); + SWAP( m_fTexCoordVelocityX ); + SWAP( m_fTexCoordVelocityY ); + SWAP(m_use_effect_clock_for_texcoords); + SWAP(m_pTexture); +#undef SWAP + return *this; +} + void Sprite::InitState() { Actor::InitState(); @@ -934,7 +959,7 @@ void Sprite::ScaleToClipped( float fWidth, float fHeight ) Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) ); // find which dimension is larger bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01; - + if( bXDimNeedsToBeCropped ) // crop X { float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth(); @@ -943,9 +968,9 @@ void Sprite::ScaleToClipped( float fWidth, float fHeight ) // generate a rectangle with new texture coordinates RectF fCustomImageRect( - fPercentageToCutOffEachSide, - 0, - 1 - fPercentageToCutOffEachSide, + fPercentageToCutOffEachSide, + 0, + 1 - fPercentageToCutOffEachSide, 1 ); SetCustomImageRect( fCustomImageRect ); } @@ -957,9 +982,9 @@ void Sprite::ScaleToClipped( float fWidth, float fHeight ) // generate a rectangle with new texture coordinates RectF fCustomImageRect( - 0, + 0, fPercentageToCutOffEachSide, - 1, + 1, 1 - fPercentageToCutOffEachSide ); SetCustomImageRect( fCustomImageRect ); } @@ -995,7 +1020,7 @@ void Sprite::CropTo( float fWidth, float fHeight ) Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) ); // find which dimension is larger bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01; - + if( bXDimNeedsToBeCropped ) // crop X { float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth(); @@ -1003,9 +1028,9 @@ void Sprite::CropTo( float fWidth, float fHeight ) // generate a rectangle with new texture coordinates RectF fCustomImageRect( - fPercentageToCutOffEachSide, - 0, - 1 - fPercentageToCutOffEachSide, + fPercentageToCutOffEachSide, + 0, + 1 - fPercentageToCutOffEachSide, 1 ); SetCustomImageRect( fCustomImageRect ); } @@ -1016,9 +1041,9 @@ void Sprite::CropTo( float fWidth, float fHeight ) // generate a rectangle with new texture coordinates RectF fCustomImageRect( - 0, + 0, fPercentageToCutOffEachSide, - 1, + 1, 1 - fPercentageToCutOffEachSide ); SetCustomImageRect( fCustomImageRect ); } diff --git a/src/Sprite.h b/src/Sprite.h index f6d907ed7b..0aacb7a294 100644 --- a/src/Sprite.h +++ b/src/Sprite.h @@ -21,6 +21,7 @@ public: Sprite(); Sprite( const Sprite &cpy ); + Sprite &operator=( Sprite other ); virtual ~Sprite(); // See explanation in source. diff --git a/src/StepMania.cpp b/src/StepMania.cpp index e163ce1253..4a78053914 100644 --- a/src/StepMania.cpp +++ b/src/StepMania.cpp @@ -96,7 +96,8 @@ void StepMania::GetPreferredVideoModeParams( VideoModeParams ¶msOut ) } paramsOut = VideoModeParams( - PREFSMAN->m_bWindowed, + PREFSMAN->m_bWindowed || PREFSMAN->m_bFullscreenIsBorderlessWindow, + PREFSMAN->m_sDisplayId, iWidth, PREFSMAN->m_iDisplayHeight, PREFSMAN->m_iDisplayColorDepth, @@ -106,6 +107,7 @@ void StepMania::GetPreferredVideoModeParams( VideoModeParams ¶msOut ) PREFSMAN->m_bSmoothLines, PREFSMAN->m_bTrilinearFiltering, PREFSMAN->m_bAnisotropicFiltering, + !PREFSMAN->m_bWindowed && PREFSMAN->m_bFullscreenIsBorderlessWindow, CommonMetrics::WINDOW_TITLE, THEME->GetPathG("Common","window icon"), PREFSMAN->m_bPAL, @@ -146,7 +148,13 @@ static void StoreActualGraphicOptions() * we don't go through the process of auto-detecting a usable video mode * every time. */ const VideoModeParams ¶ms = DISPLAY->GetActualVideoModeParams(); - PREFSMAN->m_bWindowed.Set( params.windowed ); + PREFSMAN->m_bWindowed.Set( params.windowed && !params.bWindowIsFullscreenBorderless ); + if (!params.windowed && !params.bWindowIsFullscreenBorderless) { + // In all other cases, want to preserve the value of this preference, + // but if DISPLAY decides to go fullscreen exclusive, we'll persist that decision + PREFSMAN->m_bFullscreenIsBorderlessWindow.Set( false ); + } + /* If we're windowed, we may have tweaked the width based on the aspect ratio. * Don't save this new value over the preferred value. */ diff --git a/src/arch/LowLevelWindow/LowLevelWindow.h b/src/arch/LowLevelWindow/LowLevelWindow.h index ad31aab327..af6f04f72a 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow.h +++ b/src/arch/LowLevelWindow/LowLevelWindow.h @@ -3,9 +3,10 @@ #include -class DisplayResolution; -typedef set DisplayResolutions; +class DisplaySpec; +typedef std::set DisplaySpecs; class VideoModeParams; +class ActualVideoModeParams; class RenderTarget; struct RenderTargetParam; /** @brief Handle low-level operations that OGL 1.x doesn't give us. */ @@ -22,7 +23,7 @@ public: // bNewDeviceOut is set true if a new device was created and textures // need to be reloaded. virtual RString TryVideoMode( const VideoModeParams &p, bool &bNewDeviceOut ) = 0; - virtual void GetDisplayResolutions( DisplayResolutions &out ) const = 0; + virtual void GetDisplaySpecs(DisplaySpecs &out) const = 0; virtual void LogDebugInformation() const { } virtual bool IsSoftwareRenderer( RString & /* sError */ ) { return false; } @@ -30,11 +31,13 @@ public: virtual void SwapBuffers() = 0; virtual void Update() { } - virtual const VideoModeParams &GetActualVideoModeParams() const = 0; + virtual const ActualVideoModeParams GetActualVideoModeParams() const = 0; virtual bool SupportsRenderToTexture() const { return false; } virtual RenderTarget *CreateRenderTarget() { return NULL; } + virtual bool SupportsFullscreenBorderlessWindow() const { return false; }; + virtual bool SupportsThreadedRendering() { return false; } virtual void BeginConcurrentRenderingMainThread() { } virtual void EndConcurrentRenderingMainThread() { } diff --git a/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.h b/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.h index 9dd1644616..a8585d6349 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.h +++ b/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.h @@ -22,12 +22,12 @@ public: ~LowLevelWindow_MacOSX(); void *GetProcAddress( RString s ); RString TryVideoMode( const VideoModeParams& p, bool& newDeviceOut ); - void GetDisplayResolutions( DisplayResolutions &dr ) const; + void GetDisplaySpecs( DisplaySpecs &specs ) const; void SwapBuffers(); void Update(); - const VideoModeParams &GetActualVideoModeParams() const { return m_CurrentParams; } + const ActualVideoModeParams GetActualVideoModeParams() const { return m_CurrentParams; } bool SupportsRenderToTexture() const { return true; } RenderTarget *CreateRenderTarget(); diff --git a/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm b/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm index 75e692ea17..ce9834ef9d 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm +++ b/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm @@ -1,6 +1,6 @@ #import "global.h" #import "LowLevelWindow_MacOSX.h" -#import "DisplayResolutions.h" +#import "DisplaySpec.h" #import "RageUtil.h" #import "RageThreads.h" #import "RageDisplay_OGL_Helpers.h" @@ -109,7 +109,7 @@ public: - (void) setParams:(NSValue *)params { const VideoModeParams &p = *(const VideoModeParams *)[params pointerValue]; - NSRect contentRect = { { 0, 0 }, { p.width, p.height } }; + NSRect contentRect = { { 0, 0 }, { static_cast(p.width), static_cast(p.height) } }; [m_Window setContentSize:contentRect.size]; [m_Window setTitle:[NSString stringWithUTF8String:p.sWindowTitle.c_str()]]; @@ -564,28 +564,50 @@ static bool GetBoolValue( CFTypeRef r ) return r && CFGetTypeID( r ) == CFBooleanGetTypeID() && CFBooleanGetValue( CFBooleanRef(r) ); } -void LowLevelWindow_MacOSX::GetDisplayResolutions( DisplayResolutions &dr ) const +static double GetDoubleValue( CFTypeRef r ) +{ + double ret; + + if( !r || CFGetTypeID(r) != CFNumberGetTypeID() || !CFNumberGetValue(CFNumberRef(r), kCFNumberDoubleType, &ret) ) + return 0; + return ret; +} + +static DisplayMode ConvertDisplayMode( CFDictionaryRef dict ) +{ + int width = GetIntValue( CFDictionaryGetValue(dict, kCGDisplayWidth) ); + int height = GetIntValue( CFDictionaryGetValue(dict, kCGDisplayHeight) ); + double rate = GetDoubleValue( CFDictionaryGetValue(dict, kCGDisplayRefreshRate) ); + + return { static_cast (width), static_cast (height), rate}; +} + +void LowLevelWindow_MacOSX::GetDisplaySpecs( DisplaySpecs &specs ) const { CFArrayRef modes = CGDisplayAvailableModes( kCGDirectMainDisplay ); ASSERT( modes ); const CFIndex count = CFArrayGetCount( modes ); - + + std::set available; + CFDictionaryRef currentModeDict = CGDisplayCurrentMode( kCGDirectMainDisplay ); + DisplayMode current = ConvertDisplayMode( currentModeDict ); + for( CFIndex i = 0; i < count; ++i ) { CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex( modes, i ); - int width = GetIntValue( CFDictionaryGetValue(dict, kCGDisplayWidth) ); - int height = GetIntValue( CFDictionaryGetValue(dict, kCGDisplayHeight) ); CFTypeRef safe = CFDictionaryGetValue( dict, kCGDisplayModeIsSafeForHardware ); - bool stretched = GetBoolValue( CFDictionaryGetValue(dict, kCGDisplayModeIsStretched) ); - - if( !width || !height ) + DisplayMode mode = ConvertDisplayMode( dict ); + + if( !mode.width || !mode.height ) continue; if( safe && !GetBoolValue( safe ) ) continue; - DisplayResolution res = { width, height, stretched }; - dr.insert( res ); + available.insert( mode ); } // Do not release modes! We don't own them here. + RectI bounds( 0, 0, current.width, current.height ); + DisplaySpec s( "", "Fullscreen", available, current, bounds ); + specs.insert( s ); } void LowLevelWindow_MacOSX::SwapBuffers() diff --git a/src/arch/LowLevelWindow/LowLevelWindow_Win32.cpp b/src/arch/LowLevelWindow/LowLevelWindow_Win32.cpp index 1f6b108bea..5e9e356d9a 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_Win32.cpp +++ b/src/arch/LowLevelWindow/LowLevelWindow_Win32.cpp @@ -59,9 +59,9 @@ LowLevelWindow_Win32::~LowLevelWindow_Win32() GraphicsWindow::Shutdown(); } -void LowLevelWindow_Win32::GetDisplayResolutions( DisplayResolutions &out ) const +void LowLevelWindow_Win32::GetDisplaySpecs( DisplaySpecs &out ) const { - GraphicsWindow::GetDisplayResolutions( out ); + GraphicsWindow::GetDisplaySpecs( out ); } int ChooseWindowPixelFormat( const VideoModeParams &p, PIXELFORMATDESCRIPTOR *pixfmt ) @@ -292,7 +292,7 @@ void LowLevelWindow_Win32::Update() GraphicsWindow::Update(); } -const VideoModeParams &LowLevelWindow_Win32::GetActualVideoModeParams() const +const ActualVideoModeParams LowLevelWindow_Win32::GetActualVideoModeParams() const { return GraphicsWindow::GetParams(); } diff --git a/src/arch/LowLevelWindow/LowLevelWindow_Win32.h b/src/arch/LowLevelWindow/LowLevelWindow_Win32.h index 2f371b1481..f5bc5ec281 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_Win32.h +++ b/src/arch/LowLevelWindow/LowLevelWindow_Win32.h @@ -10,7 +10,7 @@ public: ~LowLevelWindow_Win32(); void *GetProcAddress( RString s ); RString TryVideoMode( const VideoModeParams &p, bool &bNewDeviceOut ); - void GetDisplayResolutions( DisplayResolutions &out ) const; + void GetDisplaySpecs( DisplaySpecs &out ) const; bool IsSoftwareRenderer( RString &sError ); void SwapBuffers(); void Update(); @@ -20,7 +20,7 @@ public: virtual bool SupportsRenderToTexture() const { return true; } virtual RenderTarget *CreateRenderTarget(); - const VideoModeParams &GetActualVideoModeParams() const; + const ActualVideoModeParams GetActualVideoModeParams() const; }; #ifdef ARCH_LOW_LEVEL_WINDOW diff --git a/src/arch/LowLevelWindow/LowLevelWindow_X11.cpp b/src/arch/LowLevelWindow/LowLevelWindow_X11.cpp index 0fad8e81c8..85bf24391a 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_X11.cpp +++ b/src/arch/LowLevelWindow/LowLevelWindow_X11.cpp @@ -5,31 +5,54 @@ #include "archutils/Unix/X11Helper.h" #include "PrefsManager.h" // XXX #include "RageDisplay.h" // VideoModeParams -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "LocalizedString.h" #include "RageDisplay_OGL_Helpers.h" using namespace RageDisplay_Legacy_Helpers; using namespace X11Helper; -#include +#include #include // ceil() +#include #define GLX_GLXEXT_PROTOTYPES #include // All sorts of stuff... #include #include #include +#if defined(HAVE_XINERAMA) +#include +#endif #if defined(HAVE_LIBXTST) #include #endif +// Display ID for treating the entire X screen as the display +const std::string ID_XSCREEN = "XSCREEN_RANDR"; + static GLXContext g_pContext = NULL; static GLXContext g_pBackgroundContext = NULL; static Window g_AltWindow = None; -static Rotation g_OldRotation; -static int g_iOldSize; -XRRScreenConfiguration *g_pScreenConfig = NULL; +static bool g_bChangedScreenSize = false; +static SizeID g_iOldSize = None; +static Rotation g_OldRotation = RR_Rotate_0; +static XRRScreenConfiguration *g_pScreenConfig = nullptr; +static RRMode g_originalRandRMode = None; +static RROutput g_usedCrtc = None; +static int g_iRandRVerMinor = 0; +static int g_iRandRVerMajor = 0; +static bool g_bUseXRandR12 = false; +static bool g_bUseXinerama = false; + +inline float calcRandRRefresh( unsigned long iPixelClock, int iHTotal, int iVTotal ) +{ + // Pixel Clock divided by total pixels in mode, + // not just those onscreen! + return ( iPixelClock ) / ( iHTotal * iVTotal ); +} + +bool NetWMSupported(Display *Dpy, Atom feature); static LocalizedString FAILED_CONNECTION_XSERVER( "LowLevelWindow_X11", "Failed to establish a connection with the X server" ); LowLevelWindow_X11::LowLevelWindow_X11() @@ -37,6 +60,18 @@ LowLevelWindow_X11::LowLevelWindow_X11() if( !OpenXConnection() ) RageException::Throw( "%s", FAILED_CONNECTION_XSERVER.GetValue().c_str() ); + if( XRRQueryVersion( Dpy, &g_iRandRVerMajor, &g_iRandRVerMinor ) && g_iRandRVerMajor >= 1 && g_iRandRVerMinor >= 2) g_bUseXRandR12 = true; +#ifdef HAVE_XINERAMA + int xinerama_event_base = 0; + int xinerama_error_base = 0; + Atom fullscreen_monitors = XInternAtom( Dpy, "_NET_WM_FULLSCREEN_MONITORS", False ); + if (XineramaQueryExtension( Dpy, &xinerama_event_base, &xinerama_error_base ) && + NetWMSupported( Dpy, fullscreen_monitors )) + { + g_bUseXinerama = true; + } +#endif + const int iScreen = DefaultScreen( Dpy ); int iXServerVersion = XVendorRelease( Dpy ); /* eg. 40201001 */ int iMajor = iXServerVersion / 10000000; iXServerVersion %= 10000000; @@ -50,7 +85,6 @@ LowLevelWindow_X11::LowLevelWindow_X11() LOG->Info( "Client GLX vendor: %s [%s]", glXGetClientString( Dpy, GLX_VENDOR ), glXGetClientString( Dpy, GLX_VERSION ) ); m_bWasWindowed = true; g_pScreenConfig = XRRGetScreenInfo( Dpy, RootWindow(Dpy, DefaultScreen(Dpy)) ); - g_iOldSize = XRRConfigCurrentConfiguration( g_pScreenConfig, &g_OldRotation ); } LowLevelWindow_X11::~LowLevelWindow_X11() @@ -58,8 +92,7 @@ LowLevelWindow_X11::~LowLevelWindow_X11() // Reset the display if( !m_bWasWindowed ) { - XRRSetScreenConfig( Dpy, g_pScreenConfig, RootWindow(Dpy, DefaultScreen(Dpy)), g_iOldSize, g_OldRotation, CurrentTime ); - + RestoreOutputConfig(); XUngrabKeyboard( Dpy, CurrentTime ); } if( g_pContext ) @@ -72,9 +105,6 @@ LowLevelWindow_X11::~LowLevelWindow_X11() glXDestroyContext( Dpy, g_pBackgroundContext ); g_pBackgroundContext = NULL; } - XRRFreeScreenConfigInfo( g_pScreenConfig ); - g_pScreenConfig = NULL; - XDestroyWindow( Dpy, Win ); Win = None; XDestroyWindow( Dpy, g_AltWindow ); @@ -82,6 +112,29 @@ LowLevelWindow_X11::~LowLevelWindow_X11() CloseXConnection(); } +/* + * Restore saved X screen/CRTC configuration + */ +void LowLevelWindow_X11::RestoreOutputConfig() { + if (g_bChangedScreenSize) { + XRRSetScreenConfig(Dpy, g_pScreenConfig, RootWindow(Dpy, DefaultScreen(Dpy)), g_iOldSize, g_OldRotation, + CurrentTime); + } + if (g_usedCrtc != None) { + ASSERT(g_bUseXRandR12); + XRRScreenResources *res = XRRGetScreenResources(Dpy, Win); + XRRCrtcInfo *conf = XRRGetCrtcInfo(Dpy, res, g_usedCrtc); + XRRSetCrtcConfig(Dpy, res, g_usedCrtc, conf->timestamp, conf->x, conf->y, g_originalRandRMode, conf->rotation, + conf->outputs, conf->noutput); + XRRFreeScreenResources(res); + XRRFreeCrtcInfo(conf); + } + g_iOldSize = None; + g_bChangedScreenSize = false; + g_usedCrtc = None; + g_OldRotation = RR_Rotate_0; +} + void *LowLevelWindow_X11::GetProcAddress( RString s ) { // XXX: We should check whether glXGetProcAddress or @@ -92,19 +145,20 @@ void *LowLevelWindow_X11::GetProcAddress( RString s ) RString LowLevelWindow_X11::TryVideoMode( const VideoModeParams &p, bool &bNewDeviceOut ) { -#if defined(UNIX) - /* nVidia cards: - * This only works the first time we set up a window; after that, the - * drivers appear to cache the value, so you have to actually restart - * the program to change it again. */ - static char buf[128]; - strcpy( buf, "__GL_SYNC_TO_VBLANK=" ); - strcat( buf, p.vsync?"1":"0" ); - putenv( buf ); -#endif + // We're going to be interested in MapNotify/ConfigureNotify events in this routine, + // so ensure our event mask includes these, restore it on exit + XWindowAttributes winAttrib; + auto restore = [&](XWindowAttributes *attr) { XSelectInput( Dpy, Win, attr->your_event_mask );}; + auto restoreAttrib = std::unique_ptr(&winAttrib, restore); + + // These might change if we're rendering at different resolution than window + int windowWidth = p.width; + int windowHeight = p.height; + bool renderOffscreen = false; if( g_pContext == NULL || p.bpp != CurrentParams.bpp || m_bWasWindowed != p.windowed ) { + bool bFirstRun = g_pContext == NULL; // Different depth, or we didn't make a window before. New context. bNewDeviceOut = true; @@ -156,128 +210,377 @@ RString LowLevelWindow_X11::TryVideoMode( const VideoModeParams &p, bool &bNewDe glXMakeCurrent( Dpy, Win, g_pContext ); - // Map the window, ensuring we get the MapNotify event - XWindowAttributes winAttrib; XGetWindowAttributes( Dpy, Win, &winAttrib ); - XSelectInput( Dpy, Win, winAttrib.your_event_mask | StructureNotifyMask ); + XSelectInput( Dpy, Win, winAttrib.your_event_mask | StructureNotifyMask | PropertyChangeMask ); + XMapWindow( Dpy, Win ); - // Wait until we actually have a mapped window before trying to - // use it! - XEvent event; - do - { - XNextEvent( Dpy, &event ); - } while (event.type != MapNotify); + XEvent ev; + do {XWindowEvent( Dpy, Win, StructureNotifyMask, &ev );} + while ( ev.type != MapNotify); - // Set the event mask back to what it was - XSelectInput( Dpy, Win, winAttrib.your_event_mask ); + // I can't find official docs saying what happens if you re-init GLEW. + // I'll just assume the behavior is undefined. + if(bFirstRun) + { + GLenum err = glewInit(); + ASSERT( err == GLEW_OK ); + } } else { // We're remodeling the existing window, and not touching the context. bNewDeviceOut = false; + + XGetWindowAttributes( Dpy, Win, &winAttrib ); + XSelectInput( Dpy, Win, winAttrib.your_event_mask | StructureNotifyMask | PropertyChangeMask ); + + if( !p.windowed ) + { + // X11 is an asynchronous beast. If we're resizing an existing + // window directly (i.e. override-redirect as opposed to asking the + // WM to do it) and don't wait for the window to actually be + // resized, we'll get unexpected results from glViewport() etc. I + // don't know why, or why it *doesn't* break in the slower process + // of waiting for the WM to resize the window. + + // So, set the event mask so we're notified when the window is resized... + // Send the resize command... + XResizeWindow( Dpy, Win, static_cast (p.width), static_cast (p.height) ); + + // We'll wait for the notification once we've done everything else, + // to save time. + } } + float rate = 60; // Will be unchanged if windowed. Not sure I care. + if( !p.windowed ) { - if( m_bWasWindowed ) - { + RestoreOutputConfig(); + + if (p.sDisplayId == ID_XSCREEN || p.sDisplayId.empty()) { // If the user changed the resolution while StepMania was windowed we overwrite the resolution to restore with it at exit. g_iOldSize = XRRConfigCurrentConfiguration( g_pScreenConfig, &g_OldRotation ); m_bWasWindowed = false; - } - // Find a matching mode. - int iSizesXct; - XRRScreenSize *pSizesX = XRRSizes( Dpy, DefaultScreen(Dpy), &iSizesXct ); - ASSERT_M( iSizesXct != 0, "Couldn't get resolution list from X server" ); + // Find a matching mode. + int iSizesXct; + XRRScreenSize *pSizesX = XRRSizes( Dpy, DefaultScreen(Dpy), &iSizesXct ); + ASSERT_M( iSizesXct != 0, "Couldn't get resolution list from X server" ); - int iSizeMatch = -1; + int iSizeMatch = -1; - for( int i = 0; i < iSizesXct; ++i ) - { - if( pSizesX[i].width == p.width && pSizesX[i].height == p.height ) - { - iSizeMatch = i; - break; + for (int i = 0; i < iSizesXct; ++i) { + if (pSizesX[i].width == p.width && pSizesX[i].height == p.height) { + iSizeMatch = i; + break; + } + } + if (iSizeMatch != g_iOldSize) { + g_bChangedScreenSize = true; } - } - // Set this mode. - // XXX: This doesn't handle if the config has changed since we queried it (see man Xrandr) - XRRSetScreenConfig( Dpy, g_pScreenConfig, RootWindow(Dpy, DefaultScreen(Dpy)), iSizeMatch, 1, CurrentTime ); + // Set this mode. + // XXX: This doesn't handle if the config has changed since we queried it (see man Xrandr) + Status s = XRRSetScreenConfig( Dpy, g_pScreenConfig, RootWindow(Dpy, DefaultScreen(Dpy)), iSizeMatch, 1, CurrentTime ); + if (s) + { + return "Failed to set screen config"; + } + + XMoveWindow( Dpy, Win, 0, 0 ); + + XRaiseWindow( Dpy, Win ); + + // We want to prevent the WM from catching anything that comes from the keyboard. + // We should do this every time on fullscreen and not only we entering from windowed mode because we could lose focus at resolution change and that will leave the user input locked. + while (XGrabKeyboard( Dpy, Win, True, GrabModeAsync, GrabModeAsync, CurrentTime )); + + } else { + ASSERT(g_bUseXRandR12); + /* === Configuring a specific CRTC === */ + // Arcane and undocumented but PROPER XRandR 1.2 method. + // What we do is directly reconfigure the CRTC of the primary display, + // Which prevents the (RandR) screen itself from resizing, and therefore + // leaving user's desktop unmolested. + LOG->Info("LowLevelWindow_X11: Using XRandR"); + + XRRScreenResources *scrRes = XRRGetScreenResources(Dpy, Win); + ASSERT(scrRes != NULL); + ASSERT(scrRes->ncrtc > 0); + ASSERT(scrRes->noutput > 0); + ASSERT(scrRes->nmode > 0); + + // If an output name has been specified, search for it + RROutput targetOut = None; + if (p.sDisplayId.length() > 0) { + for (unsigned int i = 0; i < scrRes->noutput && targetOut == None; ++i) { + XRROutputInfo *outInfo = XRRGetOutputInfo(Dpy, scrRes, scrRes->outputs[i]); + std::string outName = std::string(outInfo->name, static_cast (outInfo->nameLen)); + if (p.sDisplayId == outName) { + targetOut = scrRes->outputs[i]; + } + XRRFreeOutputInfo(outInfo); + } + } + if (targetOut == None) { + LOG->Info("Did not find display output %s, trying another", p.sDisplayId.c_str()); + // didn't find named output, pick primary/or at least one that works + if (g_iRandRVerMajor >= 1 && g_iRandRVerMinor >= 3) { + // RandR 1.3 can tell us what the primary display is. + targetOut = XRRGetOutputPrimary(Dpy, Win); + } else { + // Only RandR 1.2. We'll look for a "Connected" output, or if we can't find that, + // (it is possible the connection state could be unknown), we'll at least + // look for an output with a CRTC driving it + RROutput connected = None, hasCrtc = None; + for (unsigned int i = 0; i < scrRes->noutput; ++i) { + XRROutputInfo *outInfo = XRRGetOutputInfo(Dpy, scrRes, scrRes->outputs[i]); + if (outInfo->connection == RR_Connected) { // Check for CONNECTED state: Connected == 0 + connected = scrRes->outputs[i]; + } + if (outInfo->crtc != None) { + hasCrtc = outInfo->crtc; + } + XRRFreeOutputInfo(outInfo); + } + targetOut = connected != None ? connected : hasCrtc; + ASSERT(targetOut != None); + } + } + + // if the target output is not currently being driven by a crtc, + // find an unused crtc that can be connected to it + XRROutputInfo *tgtOutInfo = XRRGetOutputInfo( Dpy, scrRes, targetOut ); + if (tgtOutInfo == NULL) + { + XRRFreeScreenResources(scrRes); + return "Failed to find XRROutput"; + } + + RRCrtc tgtOutCrtc = tgtOutInfo->crtc; + if (tgtOutCrtc == None) + { + for (unsigned int i = 0; i < tgtOutInfo->ncrtc; ++i) + { + XRRCrtcInfo *crtcInfo = XRRGetCrtcInfo( Dpy, scrRes, tgtOutInfo->crtcs[i] ); + if (crtcInfo->mode == None) + { + tgtOutCrtc = tgtOutInfo->crtcs[i]; + } + XRRFreeCrtcInfo( crtcInfo ); + } + } + ASSERT(tgtOutCrtc != None); + + + XRRCrtcInfo *oldConf = XRRGetCrtcInfo( Dpy, scrRes, tgtOutCrtc ); + + float fRefreshDiff = 99999; + float fRefreshRate = 0; + RRMode mode = None; + // A quirk of XRandR is that the width and height are as the display + // controller ("CRTC") sees it, which means height and width are + // flipped if there's rotation going on. + const bool bPortrait = (oldConf->rotation & (RR_Rotate_90 | RR_Rotate_270)) != 0; + // Find a mode that matches our exact wanted resolution, + // with as close to our desired refresh rate as possible. + for (int i = 0; i < scrRes->nmode; i++) { + const XRRModeInfo &thisMI = scrRes->modes[i]; + const unsigned int modeWidth = bPortrait ? thisMI.height : thisMI.width; + const unsigned int modeHeight = bPortrait ? thisMI.width : thisMI.height; + if (modeWidth == p.width && modeHeight == p.height) { + float fTempRefresh = calcRandRRefresh(thisMI.dotClock, thisMI.hTotal, thisMI.vTotal); + float fTempDiff = std::abs(p.rate - fTempRefresh); + if ((p.rate != REFRESH_DEFAULT && fTempDiff < fRefreshDiff) || + (p.rate == REFRESH_DEFAULT && fTempRefresh > fRefreshRate)) { + int j; + // Ensure that the output supports the mode + for (j = 0; j < tgtOutInfo->nmode; j++) + if (tgtOutInfo->modes[j] == scrRes->modes[i].id) { + mode = tgtOutInfo->modes[j]; + break; + } + + if (j < tgtOutInfo->nmode) { + fRefreshRate = fTempRefresh; + fRefreshDiff = fTempDiff; + } + } + } + } + rate = roundf(fRefreshRate); + + g_usedCrtc = tgtOutCrtc; + g_originalRandRMode = oldConf->mode; + + const std::string tgtOutName = std::string(tgtOutInfo->name, static_cast (tgtOutInfo->nameLen)); + LOG->Info("XRandR output config using CRTC %lu in mode %lu, driving output %s", + g_usedCrtc, mode, tgtOutName.c_str()); + // and FIRE! + Status s = XRRSetCrtcConfig(Dpy, scrRes, g_usedCrtc, oldConf->timestamp, oldConf->x, oldConf->y, mode, + oldConf->rotation, oldConf->outputs, oldConf->noutput); + if (s) { + XRRFreeCrtcInfo(oldConf); + XRRFreeOutputInfo(tgtOutInfo); + XRRFreeScreenResources(scrRes); + return "Failed to set CRTC config"; + } + + // We don't move to absolute 0,0 because that may be in the area of a different output. + // Instead we preserved the corner of our CRTC; go to that. + XMoveWindow(Dpy, Win, oldConf->x, oldConf->y); + + // Final cleanup + XRRFreeCrtcInfo(oldConf); + XRRFreeOutputInfo(tgtOutInfo); + XRRFreeScreenResources(scrRes); + } + m_bWasWindowed = false; XRaiseWindow( Dpy, Win ); // We want to prevent the WM from catching anything that comes from the keyboard. // We should do this every time on fullscreen and not only we entering from windowed mode because we could lose focus at resolution change and that will leave the user input locked. - XGrabKeyboard( Dpy, Win, True, GrabModeAsync, GrabModeAsync, CurrentTime ); + while (XGrabKeyboard( Dpy, Win, True, GrabModeAsync, GrabModeAsync, CurrentTime )); } - else + else // if(p.windowed) { if( !m_bWasWindowed ) { - XRRSetScreenConfig( Dpy, g_pScreenConfig, RootWindow(Dpy, DefaultScreen(Dpy)), g_iOldSize, g_OldRotation, CurrentTime ); - // In windowed mode, we actually want the WM to function normally. - // Release any previous grab. + // Return the display to the mode it was in before we fullscreened. + RestoreOutputConfig(); XUngrabKeyboard( Dpy, CurrentTime ); m_bWasWindowed = true; } + + Atom net_wm_state = XInternAtom( Dpy, "_NET_WM_STATE", False ); + Atom fullscreen_state = XInternAtom( Dpy, "_NET_WM_STATE_FULLSCREEN", False ); + Atom maximized_vert = XInternAtom( Dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False ); + Atom maximized_horz = XInternAtom( Dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False ); + // if FSBW, find matching monitor, move window to its origin, + // then set fullscreen hint, and set the CurrentParams.outWidth, CurrentParams.outHeight to the values of that display + // otherwise set the size hints and disable MAXIMIZED_* + if (p.bWindowIsFullscreenBorderless) + { + auto specs = DisplaySpecs{}; + GetDisplaySpecs( specs ); + auto target = std::find_if( specs.begin(), specs.end(), [&]( const DisplaySpec &spec ) { + return p.sDisplayId == spec.id() && spec.currentMode() != nullptr; + } ); + // If we didn't find a matching DisplaySpec for the requested ID, pick the first one with a current mode + if (target == specs.end()) + { + target = std::find_if( specs.begin(), specs.end(), [&]( const DisplaySpec &spec ) { + return spec.currentMode() != nullptr; + } ); + } + // If we _still_ haven't found anything (unlikely), then just give up + if (target == specs.end()) + { + return "Unable to find destination monitor for fullscreen borderless"; + } + + windowWidth = target->currentMode()->width; + windowHeight = target->currentMode()->height; + + if (windowWidth != p.width || windowHeight != p.height) + { + renderOffscreen = true; + } + + // Reset anything that might've been set previously: + // (1) Undo Min/Max size bounds + // (2) Remove FULLSCREEN/MAXIMIZED_{HORIZ,VERT} hints + // Without doing this, WM may not let us move/resize window to new display + // Give Window manager the chance to react to changes (otherwise, Mutter had problems + // properly reacting to moving a _NET_WM_STATE_FULLSCREEN window to a different output + // and fullscreen resetting FULLSCREEN hint. + XSizeHints hints; + hints.flags = 0; + XSetWMNormalHints( Dpy, Win, &hints ); +#if defined(HAVE_XINERAMA) + if (!g_bUseXinerama || !SetWMFullscreenMonitors( *target )) +#endif + { + SetWMState( winAttrib.root, Win, 0, maximized_horz ); + SetWMState( winAttrib.root, Win, 0, maximized_vert ); + SetWMState( winAttrib.root, Win, 0, fullscreen_state ); + + XFlush( Dpy ); + XResizeWindow( Dpy, Win, static_cast (windowWidth), static_cast (windowHeight) ); + XMoveWindow( Dpy, Win, target->currentBounds().left, target->currentBounds().top ); + XRaiseWindow( Dpy, Win ); + + SetWMState( winAttrib.root, Win, 1, fullscreen_state ); + SetWMState( winAttrib.root, Win, 1, maximized_horz ); + SetWMState( winAttrib.root, Win, 1, maximized_vert ); + } + } else + { + windowWidth = p.width; + windowHeight = p.height; + + SetWMState( winAttrib.root, Win, 0, fullscreen_state ); + // Make a window fixed size, don't let resize it or maximize it. + // Do this before resizing the window so that pane-style WMs (Ion, + // ratpoison) don't resize us back inappropriately. + { + XSizeHints hints; + + hints.flags = PMinSize|PMaxSize|PWinGravity; + hints.min_width = hints.max_width = windowWidth; + hints.min_height = hints.max_height = windowHeight; + hints.win_gravity = CenterGravity; + + XSetWMNormalHints( Dpy, Win, &hints ); + } + /* Workaround for metacity and compiz: if the window have the same + * resolution or higher than the screen, it gets automaximized even + * when the window is set to not let it happen. This happens when + * changing from fullscreen to window mode and our screen resolution + * is bigger. */ + { + SetWMState( winAttrib.root, Win, 1, maximized_vert ); + SetWMState( winAttrib.root, Win, 1, maximized_horz ); + + // This one is needed for compiz, if the window reaches out of bounds of the screen it becames destroyed, only the window, the program is left running. + // Commented out per the patch at http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=398 + //XMoveWindow( Dpy, Win, 0, 0 ); + } + } + + } - // NOTE: nVidia's implementation of this is broken by default. - // The only ways around this are mucking with xorg.conf or querying - // nvidia-settings with "$ nvidia-settings -t -q RefreshRate". - int rate = XRRConfigCurrentRate( g_pScreenConfig ); - - // Make a window fixed size, don't let resize it or maximize it. - // Do this before resizing the window so that pane-style WMs (Ion, - // ratpoison) don't resize us back inappropriately. - { - XSizeHints hints; - - hints.flags = PMinSize|PMaxSize|PWinGravity; - hints.min_width = hints.max_width = p.width; - hints.min_height = hints.max_height = p.height; - hints.win_gravity = CenterGravity; - - XSetWMNormalHints( Dpy, Win, &hints ); - } - - /* Workaround for metacity and compiz: if the window have the same - * resolution or higher than the screen, it gets automaximized even - * when the window is set to not let it happen. This happens when - * changing from fullscreen to window mode and our screen resolution - * is bigger. */ - { - XEvent xev; - Atom wm_state = XInternAtom(Dpy, "_NET_WM_STATE", False); - Atom maximized_vert = XInternAtom(Dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False); - Atom maximized_horz = XInternAtom(Dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = Win; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = 1; - xev.xclient.data.l[1] = maximized_vert; - xev.xclient.data.l[2] = 0; - - XSendEvent(Dpy, DefaultRootWindow(Dpy), False, SubstructureNotifyMask, &xev); - xev.xclient.data.l[1] = maximized_horz; - XSendEvent(Dpy, DefaultRootWindow(Dpy), False, SubstructureNotifyMask, &xev); - - // This one is needed for compiz, if the window reaches out of bounds of the screen it becames destroyed, only the window, the program is left running. - // Commented out per the patch at http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=398 - //XMoveWindow( Dpy, Win, 0, 0 ); - } - - // Resize the window. - XResizeWindow( Dpy, Win, p.width, p.height ); CurrentParams = p; - CurrentParams.rate = rate; + CurrentParams.windowWidth = windowWidth; + CurrentParams.windowHeight = windowHeight; + CurrentParams.renderOffscreen = renderOffscreen; + ASSERT( rate > 0 ); + CurrentParams.rate = static_cast (roundf(rate)); + + if (!p.windowed) + { + // Set our V-sync hint. + if (GLXEW_EXT_swap_control) // I haven't seen this actually implemented yet, but why not. + glXSwapIntervalEXT( Dpy, Win, CurrentParams.vsync ? 1 : 0 ); + // XXX: These two might be server-global. I should look into whether + // to try to preserve the original value on exit. +#ifdef GLXEW_MESA_swap_control // Added in 1.7. 1.6 is still common out there apparently. + else if(GLXEW_MESA_swap_control) // Haven't seen this NOT implemented yet + glXSwapIntervalMESA( CurrentParams.vsync ? 1 : 0 ); +#endif + else if (GLXEW_SGI_swap_control) // But old GLEW. + glXSwapIntervalSGI( CurrentParams.vsync ? 1 : 0 ); + else + CurrentParams.vsync = false; // Assuming it's not on + } + + + + return ""; // Success } @@ -332,16 +635,114 @@ void LowLevelWindow_X11::SwapBuffers() } } -void LowLevelWindow_X11::GetDisplayResolutions( DisplayResolutions &out ) const -{ - int iSizesXct; - XRRScreenSize *pSizesX = XRRSizes( Dpy, DefaultScreen( Dpy ), &iSizesXct ); - ASSERT_M( iSizesXct != 0, "Couldn't get resolution list from X server" ); +void LowLevelWindow_X11::GetDisplaySpecs(DisplaySpecs &out) const { + int screenNum = DefaultScreen(Dpy); + Screen *screen = ScreenOfDisplay(Dpy, screenNum); - for( int i = 0; i < iSizesXct; ++i ) - { - DisplayResolution res = { pSizesX[i].width, pSizesX[i].height, true }; - out.insert( res ); + XWindowAttributes winAttr = XWindowAttributes(); + if (XGetWindowAttributes(Dpy, Win, &winAttr)) { + screen = winAttr.screen; + screenNum = XScreenNumberOfScreen(screen); + } + + // Create a display spec for the entire X screen itself + // First get current config + Rotation curRotation; + XRRScreenConfiguration *screenConf = XRRGetScreenInfo(Dpy, Win); + const short curRate = XRRConfigCurrentRate(screenConf); + SizeID curSizeId = XRRConfigCurrentConfiguration(screenConf, &curRotation); + // curRotation does not factor into how we report supported XScreen sizes: + // XRR reports the supported *screen* sizes with height/width swapped appropriately + // for currently configured rotation. Supported sizes for *output* modes (below) + // DO NOT account for screen rotation + + std::set screenModes; + int nsizes = 0; + XRRScreenSize *screenSizes = XRRSizes( Dpy, screenNum, &nsizes); + DisplayMode screenCurMode = {0}; + for (unsigned int szIdx = 0, mode_idx = 0; szIdx < nsizes; ++szIdx) { + XRRScreenSize &size = screenSizes[szIdx]; + int nrates = 0; + short *rates = XRRRates(Dpy, screenNum, szIdx, &nrates); + for (unsigned int rIdx = 0; rIdx < nrates; ++rIdx, ++mode_idx) { + DisplayMode m = {static_cast (size.width), static_cast (size.height), static_cast (rates[rIdx])}; + screenModes.insert(m); + if (rates[rIdx] == curRate && szIdx == curSizeId) { + screenCurMode = m; + } + } + } + const RectI screenBounds( 0, 0, screenSizes[curSizeId].width, screenSizes[curSizeId].height); + const DisplaySpec screenSpec( ID_XSCREEN, "X Screen", screenModes, screenCurMode, screenBounds, true); + out.insert(screenSpec); + // XRRScreenSize array from XRRSizes does *not* have to be returned (valgrind said XFree was an invalid + // free in a small test program, there is no XRRFreeScreenSize, etc) + XRRFreeScreenConfigInfo(screenConf); + + if (g_bUseXRandR12) { + // Build per-output DisplaySpecs + + // First, get the list of resolutions that'll be referenced (by RRMode) in each + // OutputInfo + XRRScreenResources *scrRes = XRRGetScreenResources(Dpy, Win); + std::map outputModes; + for (unsigned int i = 0; i < scrRes->nmode; ++i) { + const XRRModeInfo &mode = scrRes->modes[i]; + DisplayMode m = {mode.width, mode.height, + calcRandRRefresh(mode.dotClock, mode.hTotal, mode.vTotal)}; + outputModes[mode.id] = m; + } + + // Now, for each output, build a corresponding DisplaySpec + for (unsigned int outIdx = 0; outIdx < scrRes->noutput; ++outIdx) + { + XRROutputInfo *outInfo = XRRGetOutputInfo( Dpy, scrRes, scrRes->outputs[outIdx] ); + if (outInfo->nmode > 0) + { + // Get the current configuration of the Output, if it's being driven by + // a crtc + RRMode curRRMode = None; + bool bPortrait = false; + int crtcX = 0, crtcY = 0; + if (outInfo->crtc != None) + { + XRRCrtcInfo *conf = XRRGetCrtcInfo( Dpy, scrRes, outInfo->crtc ); + curRRMode = conf->mode; + bPortrait = (conf->rotation & (RR_Rotate_90 | RR_Rotate_270)) != 0; + crtcX = conf->x; + crtcY = conf->y; + XRRFreeCrtcInfo( conf ); + } + // Get all supported modes, noting which one, if any, is currently active + std::set outputSupported; + DisplayMode outputCurMode = {0}; + RectI outBounds; + for (unsigned int modeIdx = 0; modeIdx < outInfo->nmode; ++modeIdx) + { + DisplayMode mode = outputModes[outInfo->modes[modeIdx]]; + unsigned int modeWidth = bPortrait ? mode.height : mode.width; + unsigned int modeHeight = bPortrait ? mode.width : mode.height; + DisplayMode m = {modeWidth, modeHeight, mode.refreshRate}; + outputSupported.insert( m ); + if (curRRMode != None && outInfo->modes[modeIdx] == curRRMode) + { + outputCurMode = m; + outBounds = RectI( crtcX, crtcY, crtcX + modeWidth, crtcY + modeHeight); + } + } + const std::string outId( outInfo->name, static_cast (outInfo->nameLen) ); + const std::string outName( outId ); + if (curRRMode != None) + { + out.insert( DisplaySpec( outId, outName, outputSupported, outputCurMode, outBounds )); + } else + { + out.insert( DisplaySpec( outId, outName, outputSupported )); + } + } + XRRFreeOutputInfo( outInfo ); + } + XRRFreeScreenResources( scrRes ); } } @@ -512,6 +913,34 @@ bool LowLevelWindow_X11::SupportsRenderToTexture() const return true; } +bool NetWMSupported(Display *Dpy, Atom feature) +{ + Atom net_supported = XInternAtom( Dpy, "_NET_SUPPORTED", False ); + Atom actual_type_return = BadAtom; + int actual_format_return = 0; + unsigned long nitems_return = 0; + unsigned long bytes_after_return = 0; + Atom *prop_return; + Status status = XGetWindowProperty( Dpy, RootWindow( Dpy, DefaultScreen( Dpy )), net_supported, 0, 8192, False, + XA_ATOM, &actual_type_return, + &actual_format_return, &nitems_return, &bytes_after_return, + reinterpret_cast (&prop_return)); + if (status != Success) + { + return false; + } + + auto supported = std::find( prop_return, prop_return + nitems_return, feature ) != prop_return + nitems_return; + XFree( prop_return ); + return supported; +} + +bool LowLevelWindow_X11::SupportsFullscreenBorderlessWindow() const +{ + Atom fullscreen = XInternAtom( Dpy, "_NET_WM_STATE_FULLSCREEN", False ); + return NetWMSupported( Dpy, fullscreen ); +} + RenderTarget *LowLevelWindow_X11::CreateRenderTarget() { return new RenderTarget_X11( this ); diff --git a/src/arch/LowLevelWindow/LowLevelWindow_X11.h b/src/arch/LowLevelWindow/LowLevelWindow_X11.h index e2fee3544c..4b830e485b 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_X11.h +++ b/src/arch/LowLevelWindow/LowLevelWindow_X11.h @@ -18,13 +18,15 @@ public: bool IsSoftwareRenderer( RString &sError ); void SwapBuffers(); - const VideoModeParams &GetActualVideoModeParams() const { return CurrentParams; } + const ActualVideoModeParams GetActualVideoModeParams() const { return CurrentParams; } - void GetDisplayResolutions( DisplayResolutions &out ) const; + void GetDisplaySpecs(DisplaySpecs &out) const; bool SupportsRenderToTexture() const; RenderTarget *CreateRenderTarget(); + bool SupportsFullscreenBorderlessWindow() const; + bool SupportsThreadedRendering(); void BeginConcurrentRenderingMainThread(); void EndConcurrentRenderingMainThread(); @@ -32,8 +34,10 @@ public: void EndConcurrentRendering(); private: + void RestoreOutputConfig(); + bool m_bWasWindowed; - VideoModeParams CurrentParams; + ActualVideoModeParams CurrentParams; }; #ifdef ARCH_LOW_LEVEL_WINDOW diff --git a/src/archutils/Darwin/arch_setup.h b/src/archutils/Darwin/arch_setup.h index 1b0ac92c4d..f2be69976c 100644 --- a/src/archutils/Darwin/arch_setup.h +++ b/src/archutils/Darwin/arch_setup.h @@ -5,7 +5,6 @@ extern "C" int sm_main( int argc, char *argv[] ); #define HAVE_CXA_DEMANGLE -#define HAVE_PTHREAD_COND_TIMEDWAIT /* This must be defined to 1 because autoconf's AC_CHECK_DECLS macro decides to define * this in all cases. If only they could be consistent... */ #define HAVE_DECL_SIGUSR1 1 diff --git a/src/archutils/Unix/X11Helper.cpp b/src/archutils/Unix/X11Helper.cpp index 78a315cad6..c1a19d7bd1 100644 --- a/src/archutils/Unix/X11Helper.cpp +++ b/src/archutils/Unix/X11Helper.cpp @@ -129,6 +129,23 @@ bool X11Helper::MakeWindow( Window &win, int screenNum, int depth, Visual *visua return true; } +void X11Helper::SetWMState( const Window &root, const Window &win, const long action, const Atom atom ) +{ + if ( !Dpy ) + return; + Atom wm_state = XInternAtom(Dpy, "_NET_WM_STATE", False); + XEvent xev; + memset( &xev, 0, sizeof( xev )); + xev.type = ClientMessage; + xev.xclient.window = Win; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = action; // 0 = Remove, 1 = Add, 2 = Toggle + xev.xclient.data.l[1] = atom; + xev.xclient.data.l[2] = 0; // end list of Atoms + XSendEvent( Dpy, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &xev ); +} + int ErrorCallback( Display *d, XErrorEvent *err ) { char errText[512]; @@ -144,6 +161,85 @@ int FatalCallback( Display *d ) RageException::Throw( "Fatal I/O error communicating with X server." ); } +#ifdef HAVE_XINERAMA +#include + +bool X11Helper::SetWMFullscreenMonitors( const DisplaySpec &target ) +{ + int num_screens = 0; + XineramaScreenInfo *screens = XineramaQueryScreens( Dpy, &num_screens ); + if (screens == nullptr) + { + return false; + } + + XineramaScreenInfo *end = screens + num_screens; + RectI monitors{}; + bool found_bounds = false; + + if (target.isVirtual()) + { + auto topmost = std::min_element( screens, end, []( XineramaScreenInfo &a, XineramaScreenInfo &b ) { + return a.y_org < b.y_org; + } ); + monitors.top = topmost->screen_number; + + auto bottommost = std::max_element( screens, end, []( XineramaScreenInfo &a, XineramaScreenInfo &b ) { + return a.y_org < b.y_org; + } ); + monitors.bottom = bottommost->screen_number; + + auto leftmost = std::min_element( screens, end, []( XineramaScreenInfo &a, XineramaScreenInfo &b ) { + return a.x_org < b.x_org; + } ); + monitors.left = leftmost->screen_number; + + auto rightmost = std::max_element( screens, end, []( XineramaScreenInfo &a, XineramaScreenInfo &b ) { + return a.x_org < b.x_org; + } ); + monitors.right = rightmost->screen_number; + found_bounds = true; + } + else if (target.currentMode() != nullptr) + { + auto mon = std::find_if( screens, end, [&]( XineramaScreenInfo &screen ) { + return screen.x_org == target.currentBounds().left && screen.y_org == target.currentBounds().top + && screen.width == target.currentMode()->width && screen.height == target.currentMode()->height; + } ); + if (mon != end) + { + monitors.left = monitors.right = monitors.top = monitors.bottom = mon->screen_number; + found_bounds = true; + } + } + + XFree( screens ); + XWindowAttributes attr = {0}; + if (!found_bounds || !XGetWindowAttributes( Dpy, Win, &attr )) + { + return false; + } + + SetWMState( attr.root, Win, 1, XInternAtom( Dpy, "_NET_WM_STATE_FULLSCREEN", False )); + + XClientMessageEvent xclient = {0}; + xclient.type = ClientMessage; + xclient.window = Win; + xclient.message_type = XInternAtom( Dpy, "_NET_WM_FULLSCREEN_MONITORS", False ); + xclient.format = 32; + xclient.data.l[0] = monitors.top; + xclient.data.l[1] = monitors.bottom; + xclient.data.l[2] = monitors.left; + xclient.data.l[3] = monitors.right; + xclient.data.l[4] = 1; + XSendEvent( Dpy, attr.root, False, SubstructureRedirectMask | SubstructureNotifyMask, + reinterpret_cast (&xclient)); + XFlush( Dpy ); + + return true; +} +#endif + /* * (c) 2005, 2006 Ben Anderson, Steve Checkoway * All rights reserved. diff --git a/src/archutils/Unix/X11Helper.h b/src/archutils/Unix/X11Helper.h index a127c2d54a..a23e5e9b27 100644 --- a/src/archutils/Unix/X11Helper.h +++ b/src/archutils/Unix/X11Helper.h @@ -4,6 +4,8 @@ #include // Window #include +#include + namespace X11Helper { // All functions in here that return a bool return true on success, and @@ -25,6 +27,12 @@ namespace X11Helper // (Re)create the Window win. bool MakeWindow( Window &win, int screenNum, int depth, Visual *visual, int width, int height, bool overrideRedirect ); + + void SetWMState( const Window &root, const Window &win, const long action, const Atom atom ); + +#ifdef HAVE_XINERAMA + bool SetWMFullscreenMonitors( const DisplaySpec &target ); +#endif }; #endif diff --git a/src/archutils/Win32/GraphicsWindow.cpp b/src/archutils/Win32/GraphicsWindow.cpp index 81f5836270..f142c9096c 100644 --- a/src/archutils/Win32/GraphicsWindow.cpp +++ b/src/archutils/Win32/GraphicsWindow.cpp @@ -4,7 +4,7 @@ #include "RageLog.h" #include "RageUtil.h" #include "RageDisplay.h" -#include "DisplayResolutions.h" +#include "DisplaySpec.h" #include "arch/ArchHooks/ArchHooks.h" #include "archutils/Win32/AppInstance.h" #include "archutils/Win32/Crash.h" @@ -539,29 +539,53 @@ HWND GraphicsWindow::GetHwnd() return g_hWndMain; } -void GraphicsWindow::GetDisplayResolutions( DisplayResolutions &out ) +void GraphicsWindow::GetDisplaySpecs( DisplaySpecs &out ) { - DEVMODE dm; - ZERO( dm ); - dm.dmSize = sizeof(dm); - int i=0; - while(EnumDisplaySettings(NULL, i++, &dm)) - { + const size_t DM_DRIVER_EXTRA_BYTES = 4096; + const size_t DMSIZE = sizeof( DEVMODE ) + DM_DRIVER_EXTRA_BYTES; + auto reset = [=]( std::unique_ptr &p ) { + ::memset( p.get(), 0, DMSIZE ); + p->dmSize = sizeof( DEVMODE ); + p->dmDriverExtra = static_cast (DM_DRIVER_EXTRA_BYTES); + }; + auto isvalid = []( std::unique_ptr &dm ) { // Windows 8 and later don't support less than 32bpp, so don't even test - // for them. GetDisplayResolutions is only for resolutions anyway. -Kyz - if(dm.dmBitsPerPel < 32) + // for them. GetDisplaySpecs only tracks resolution/refresh rate anyway. -Kyz, drewbarbs + return (dm->dmFields & DM_PELSWIDTH) && (dm->dmFields & DM_PELSHEIGHT) && (dm->dmFields & DM_DISPLAYFREQUENCY) + && (dm->dmBitsPerPel >= 32 || !(dm->dmFields & DM_BITSPERPEL)); + }; + + std::unique_ptr dm( static_cast (operator new(DMSIZE)) ); + reset( dm ); + + int i = 0; + std::set modes; + while ( EnumDisplaySettingsEx( nullptr, i++, dm.get(), 0 ) ) + { + if ( isvalid( dm ) && ChangeDisplaySettingsEx( nullptr, dm.get(), nullptr, CDS_TEST, nullptr ) == DISP_CHANGE_SUCCESSFUL ) { - continue; - } - DisplayResolution res = { dm.dmPelsWidth, dm.dmPelsHeight }; - std::set::iterator entry= out.find(res); - if(entry == out.end()) - { - if(ChangeDisplaySettings(&dm, CDS_TEST)==DISP_CHANGE_SUCCESSFUL) - { - out.insert(res); - } + DisplayMode m = { dm->dmPelsWidth, dm->dmPelsHeight, static_cast (dm->dmDisplayFrequency) }; + modes.insert(m); } + reset( dm ); + } + + reset( dm ); + // Get the current display mode + if ( EnumDisplaySettingsEx( nullptr, ENUM_CURRENT_SETTINGS, dm.get(), 0 ) && isvalid( dm ) ) + { + DisplayMode m = { dm->dmPelsWidth, dm->dmPelsHeight, static_cast (dm->dmDisplayFrequency) }; + RectI bounds = { 0, 0, static_cast (m.width), static_cast (m.height) }; + out.insert( DisplaySpec( "", "Fullscreen", modes, m, bounds ) ); + } + else if ( !modes.empty() ) + { + LOG->Warn( "Could not retrieve valid current display mode" ); + out.insert( DisplaySpec( "", "Fullscreen", *modes.begin() ) ); + } + else + { + LOG->Warn( "Could not retrieve *any* DisplaySpec's!" ); } } diff --git a/src/archutils/Win32/GraphicsWindow.h b/src/archutils/Win32/GraphicsWindow.h index 7d3cd08e6a..8a41cb2864 100644 --- a/src/archutils/Win32/GraphicsWindow.h +++ b/src/archutils/Win32/GraphicsWindow.h @@ -2,7 +2,7 @@ #define GRAPHICS_WINDOW_H #include -#include "DisplayResolutions.h" +#include "DisplaySpec.h" class VideoModeParams; class DisplayResolution; @@ -28,7 +28,7 @@ namespace GraphicsWindow void CreateGraphicsWindow( const VideoModeParams &p, bool bForceRecreateWindow = false ); void DestroyGraphicsWindow(); - void GetDisplayResolutions( DisplayResolutions &out ); + void GetDisplaySpecs( DisplaySpecs &out ); const VideoModeParams &GetParams(); HDC GetHDC();