5 1 new backport x11 fs rework (#1485)

* Use XRandR 1.2 to set fullscreen resolution for single output

Squash of roothorick's PR #497
(also includes Kyzentun's CMake changes from PR #716)

* Cherry-pick json c++1x stuff (b9e3d7174e)

* Cherry-pick c++11 support from 5bba5c0038 and 9f8b045309

* rework Linux (X11) fullscreen, improve display-related Graphics Options

Implement option to select between monitors for exclusive fullscreen mode
on X11 (using XRandR 1.2), or use a fullscreen borderless window.

Reimplement resolution/refresh rate/display mode-related option rows
using Lua, update choices dynamically so only known-good groupings of
resolution/refresh rate/aspect ratio can be selected.

Minimally update Windows/MacOS LowLevelWindow implementations to support
changes made for Linux side. Fullscreen Borderless Window/multi monitor
support from X11 not implemented for those in this commit.

* allow forcibly disabling xinerama use on Linux

When libXinerama is available, SM tries to use it to find the proper
monitor indexes to use to set _NET_WM_FULLSCREEN_MONITORS (on borderless
fullscreen). xfwm4 seems to assume that monitors are numbered in increasing
order from left to right (rather than using the Xinerama-assigned numbers),
so _NET_WM_FULLSCREEN_MONITORS misbehaves on Xfce.

This commit bypasses use of libXinerama, and instead forces SM to induce fullscreen
on the desired monitor in the backup, hacky way: remove all window hints, move window
to desired monitor, then add _NET_WM_STATE_FULLSCREEN hint. This works on
mutter and Xfce.

* Remove multiple warnings on redundant define.

This used to be hard-coded due to pthread related items, but now it's dynamically determined.

* fix _fallback menu behavior for unrecognized aspect ratios

* Fix error recreating existing FS texture

* Bump deployment target to 10.7 to use libc++ on XCode 8

* Add explicit casts to please clang

* Update changelog
This commit is contained in:
Drew Barbarello
2017-06-18 08:55:16 -07:00
committed by Colby Klein
parent 7ef14c340d
commit 557be7cf1b
57 changed files with 2616 additions and 370 deletions
+7 -4
View File
@@ -3,9 +3,10 @@
#include <set>
class DisplayResolution;
typedef set<DisplayResolution> DisplayResolutions;
class DisplaySpec;
typedef std::set<DisplaySpec> 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() { }
@@ -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();
@@ -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<CGFloat>(p.width), static_cast<CGFloat>(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<unsigned int> (width), static_cast<unsigned int> (height), rate};
}
void LowLevelWindow_MacOSX::GetDisplaySpecs( DisplaySpecs &specs ) const
{
CFArrayRef modes = CGDisplayAvailableModes( kCGDirectMainDisplay );
ASSERT( modes );
const CFIndex count = CFArrayGetCount( modes );
std::set<DisplayMode> 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()
@@ -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();
}
@@ -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
+545 -116
View File
@@ -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 <stack>
#include <set>
#include <math.h> // ceil()
#include <GL/glxew.h>
#define GLX_GLXEXT_PROTOTYPES
#include <GL/glx.h> // All sorts of stuff...
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrandr.h>
#if defined(HAVE_XINERAMA)
#include <X11/extensions/Xinerama.h>
#endif
#if defined(HAVE_LIBXTST)
#include <X11/extensions/XTest.h>
#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<XWindowAttributes, decltype(restore)>(&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<unsigned int> (p.width), static_cast<unsigned int> (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<unsigned int> (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<unsigned int> (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<unsigned int> (windowWidth), static_cast<unsigned int> (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<int> (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<DisplayMode> 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<unsigned int> (size.width), static_cast<unsigned int> (size.height), static_cast<double> (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<RRMode, DisplayMode> 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<DisplayMode> 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<unsigned int> (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<unsigned char **> (&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 );
+7 -3
View File
@@ -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