Explorar el Código

WIP: Linux port
- Fixed mouse relative input axis queries
- Fixed function for querying the window under the mouse pointer
- Fixed window close event so it cleanly exits instead of crashing
- Hooked up X11 error handler so trivial error no longer crash the app
- Reworked resolution switching so scaling is handled properly
- Fixed window initialization using invalid values
- Fixed a crash due to unintialized atomic in the task scheduler

Marko Pintera hace 8 años
padre
commit
8eae8647bd

+ 9 - 9
Source/BansheeCore/Input/BsInputFwd.h

@@ -392,15 +392,15 @@ namespace bs
 	/**	Common input axis types. */
 	enum class InputAxis
 	{
-		MouseX, /**< Mouse axis X. */
-		MouseY, /**< Mouse axis Y. */
-		MouseZ, /**< Mouse wheel/scroll axis. */
-		LeftStickX, /**< Gamepad left stick X */
-		LeftStickY, /**<  Gamepad left stick Y */
-		RightStickX, /**< Gamepad right stick X */
-		RightStickY, /**< Gamepad right stick Y */
-		LeftTrigger, /**< Gamepad left trigger */
-		RightTrigger, /**< Gamepad right trigger */
+		MouseX, /**< Mouse axis X. Provides unnormalized relative movement. */
+		MouseY, /**< Mouse axis Y. Provides unnormalized relative movement. */
+		MouseZ, /**< Mouse wheel/scroll axis. Provides unnormalized relative movement. */
+		LeftStickX, /**< Gamepad left stick X. Provides normalized ([-1, 1] range) absolute position. */
+		LeftStickY, /**<  Gamepad left stick Y. Provides normalized ([-1, 1] range) absolute position. */
+		RightStickX, /**< Gamepad right stick X. Provides normalized ([-1, 1] range) absolute position.*/
+		RightStickY, /**< Gamepad right stick Y. Provides normalized ([-1, 1] range) absolute position. */
+		LeftTrigger, /**< Gamepad left trigger. Provides normalized ([-1, 1] range) absolute position. */
+		RightTrigger, /**< Gamepad right trigger. Provides normalized ([-1, 1] range) absolute position. */
 		Count // Keep at end
 	};
 

+ 3 - 3
Source/BansheeCore/Linux/BsLinuxMouse.cpp

@@ -131,13 +131,13 @@ namespace bs
 						switch(events[j].code)
 						{
 						case REL_X:
-							relX = events[j].value;
+							relX += events[j].value;
 							break;
 						case REL_Y:
-							relY = events[i].value;
+							relY += events[j].value;
 							break;
 						case REL_WHEEL:
-							relZ = events[i].value;
+							relZ += events[j].value;
 							break;
 						default:
 							break;

+ 77 - 11
Source/BansheeCore/Linux/BsLinuxPlatform.cpp

@@ -51,7 +51,8 @@ namespace bs
 		::Display* xDisplay = nullptr;
 		::Window mainXWindow = 0;
 		::Window fullscreenXWindow = 0;
-		std::unordered_map<::Window, LinuxWindow*> windowMap;
+		UnorderedMap<::Window, LinuxWindow*> windowMap;
+		Vector<::Window> toDestroy;
 		Mutex lock;
 
 		XIM IM;
@@ -186,6 +187,59 @@ namespace bs
 			applyCurrentCursor(data, entry.first);
 	}
 
+	/**
+	 * Searches the window hierarchy, from top to bottom, looking for the top-most window that contains the specified
+	 * point. Returns 0 if one is not found.
+	 */
+	::Window getWindowUnderPoint(::Display* display, ::Window rootWindow, ::Window window, const Vector2I& screenPos)
+	{
+		::Window outRoot, outParent;
+		::Window* children;
+		UINT32 numChildren;
+		XQueryTree(display, window, &outRoot, &outParent, &children, &numChildren);
+
+		if(children == nullptr || numChildren == 0)
+			return window;
+
+		for(UINT32 j = 0; j < numChildren; j++)
+		{
+			::Window curWindow = children[numChildren - j - 1];
+
+			XWindowAttributes xwa;
+			XGetWindowAttributes(display, curWindow, &xwa);
+
+			if(xwa.map_state != IsViewable || xwa.c_class != InputOutput)
+				continue;
+
+			// Get position in root window coordinates
+			::Window outChild;
+			Vector2I pos;
+			if(!XTranslateCoordinates(display, curWindow, rootWindow, 0, 0, &pos.x, &pos.y, &outChild))
+				continue;
+
+			Rect2I area(pos.x, pos.y, (UINT32)xwa.width, (UINT32)xwa.height);
+			if(area.contains(screenPos))
+			{
+				XFree(children);
+				return getWindowUnderPoint(display, rootWindow, curWindow, screenPos);
+			}
+		}
+
+		XFree(children);
+		return 0;
+	}
+
+	int x11ErrorHandler(::Display* display, XErrorEvent* event)
+	{
+		// X11 by default crashes the app on error, even though some errors can be just fine. So we provide our own handler.
+
+		char buffer[256];
+		XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
+		LOGWRN("X11 error: " + String(buffer));
+
+		return 0;
+	}
+
 	Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
 
 	Platform::~Platform()
@@ -233,19 +287,14 @@ namespace bs
 		window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
 		::Window xWindow = linuxWindow->_getXWindow();
 
-		Vector2I pos;
 		UINT32 screenCount = (UINT32)XScreenCount(mData->xDisplay);
 
 		for (UINT32 i = 0; i < screenCount; ++i)
 		{
-			::Window outRoot, outChild;
-			INT32 childX, childY;
-			UINT32 mask;
-			if(XQueryPointer(mData->xDisplay, XRootWindow(mData->xDisplay, i), &outRoot, &outChild, &pos.x,
-					&pos.y, &childX, &childY, &mask))
-			{
-				return outChild == xWindow;
-			}
+			::Window rootWindow = XRootWindow(mData->xDisplay, i);
+
+			::Window curWindow = getWindowUnderPoint(mData->xDisplay, rootWindow, rootWindow, screenPos);
+			return curWindow == xWindow;
 		}
 
 		return false;
@@ -551,7 +600,16 @@ namespace bs
 		{
 			Lock lock(mData->lock);
 			if(XPending(mData->xDisplay) <= 0)
+			{
+				// No more events, destroy any queued windows
+				for(auto& entry : mData->toDestroy)
+					XDestroyWindow(mData->xDisplay, entry);
+
+				mData->toDestroy.clear();
+				XSync(mData->xDisplay, false);
+
 				break;
+			}
 
 			XEvent event;
 			XNextEvent(mData->xDisplay, &event);
@@ -564,7 +622,14 @@ namespace bs
 					break;
 
 				if((Atom)event.xclient.data.l[0] == mData->atomDeleteWindow)
-					XDestroyWindow(mData->xDisplay, event.xclient.window);
+				{
+					// We queue the window for destruction as soon as we process all current events (since some of those
+					// events could still refer to this window)
+					mData->toDestroy.push_back(event.xclient.window);
+
+					XUnmapWindow(mData->xDisplay, event.xclient.window);
+					XSync(mData->xDisplay, false);
+				}
 			}
 				break;
 			case DestroyNotify:
@@ -931,6 +996,7 @@ namespace bs
 	{
 		Lock lock(mData->lock);
 		mData->xDisplay = XOpenDisplay(nullptr);
+		XSetErrorHandler(x11ErrorHandler);
 
 		if(XSupportsLocale())
 		{

+ 8 - 8
Source/BansheeCore/Managers/BsTextureManager.cpp

@@ -7,8 +7,8 @@
 
 namespace bs 
 {
-    SPtr<Texture> TextureManager::createTexture(const TEXTURE_DESC& desc)
-    {
+	SPtr<Texture> TextureManager::createTexture(const TEXTURE_DESC& desc)
+	{
 		Texture* tex = new (bs_alloc<Texture>()) Texture(desc);
 		SPtr<Texture> ret = bs_core_ptr<Texture>(tex);
 
@@ -16,10 +16,10 @@ namespace bs
 		ret->initialize();
 
 		return ret;
-    }
+	}
 
 	SPtr<Texture> TextureManager::createTexture(const TEXTURE_DESC& desc, const SPtr<PixelData>& pixelData)
-    {
+	{
 		Texture* tex = new (bs_alloc<Texture>()) Texture(desc, pixelData);
 		SPtr<Texture> ret = bs_core_ptr<Texture>(tex);
 
@@ -27,7 +27,7 @@ namespace bs
 		ret->initialize();
 
 		return ret;
-    }
+	}
 
 	SPtr<Texture> TextureManager::_createEmpty()
 	{
@@ -129,15 +129,15 @@ namespace bs
 
 		normalTexture->writeData(*normalPixelData);
 		Texture::NORMAL = normalTexture;
-    }
+	}
 
 	void TextureManager::onShutDown()
-    {
+	{
 		// Need to make sure these are freed while still on the core thread
 		Texture::WHITE = nullptr;
 		Texture::BLACK = nullptr;
 		Texture::NORMAL = nullptr;
-    }
+	}
 
 	SPtr<Texture> TextureManager::createTexture(const TEXTURE_DESC& desc, GpuDeviceFlags deviceMask)
 	{

+ 2 - 6
Source/BansheeEngine/Input/BsInputConfiguration.cpp

@@ -18,12 +18,8 @@ namespace bs
 		:buttonCode(buttonCode), modifiers(modifiers), repeatable(repeatable)
 	{ }
 
-	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC()
-		:deadZone(0.0001f), sensitivity(1.0f), invert(false), type((UINT32)InputAxis::MouseX)
-	{ }
-
-	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC(UINT32 type, float deadZone, float sensitivity, bool invert)
-		:deadZone(deadZone), sensitivity(sensitivity), invert(invert), type(type)
+	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC(UINT32 type)
+		:type(type)
 	{ }
 
 	VirtualButton::VirtualButton()

+ 21 - 11
Source/BansheeEngine/Input/BsInputConfiguration.h

@@ -40,23 +40,33 @@ namespace bs
 	 */
 	struct BS_EXPORT VIRTUAL_AXIS_DESC
 	{
-		VIRTUAL_AXIS_DESC();
+		VIRTUAL_AXIS_DESC() {}
 
 		/**
 		 * Constructs a new virtual axis descriptor.
 		 *
-		 * @param[in]	type		Type of physical axis to map to. See InputAxis type for common types, but you are not 
-		 *							limited to those values.
-		 * @param[in]	deadZone	Value below which to ignore axis value and consider it 0.
-		 * @param[in]	sensitivity	Higher sensitivity means the axis will more easily reach its maximum values.
-		 * @param[in]	invert		Should axis values be inverted.
+		 * @param[in]	type		@copydoc VIRTUAL_AXIS_DESC::type
 		 */
-		VIRTUAL_AXIS_DESC(UINT32 type, float deadZone = 0.0001f, float sensitivity = 1.0f, bool invert = false);
+		VIRTUAL_AXIS_DESC(UINT32 type);
 
-		float deadZone;
-		float sensitivity;
-		bool invert;
-		UINT32 type;
+		/** Type of physical axis to map to. See InputAxis type for common types, but you are not limited to those values. */
+		UINT32 type = (UINT32)InputAxis::MouseX;
+
+		/** Value below which to ignore axis value and consider it 0. */
+		float deadZone = 0.0001f;
+
+		/** Higher sensitivity means the axis will more easily reach its maximum values. */
+		float sensitivity = 1.0f;
+
+		/** Should the axis be inverted. */
+		bool invert = false;
+
+		/**
+		 * If enabled, axis values will be normalized to [-1, 1] range. Most axes already come in normalized form and this
+		 * value will not affect such axes. Some axes, like mouse movement are not normalized by default and will instead
+		 * report relative movement. By enabling this you will normalize such axes to [-1, 1] range.
+		 */
+		bool normalize = false;
 	};
 
 	/**

+ 17 - 2
Source/BansheeEngine/Input/BsVirtualInput.cpp

@@ -82,7 +82,10 @@ namespace bs
 		{
 			float axisValue = gInput().getAxisValue((UINT32)axisDesc.type, deviceIdx);
 
-			if (axisDesc.deadZone > 0.0f)
+			bool isMouseAxis = (UINT32)axisDesc.type <= (UINT32)InputAxis::MouseZ;
+			bool isNormalized = axisDesc.normalize || !isMouseAxis;
+
+			if (isNormalized && axisDesc.deadZone > 0.0f)
 			{
 				// Scale to [-1, 1] range after removing the dead zone
 				if (axisValue > 0)
@@ -91,7 +94,19 @@ namespace bs
 					axisValue = -std::max(0.f, -axisValue - axisDesc.deadZone) / (1.0f - axisDesc.deadZone);
 			}
 
-			axisValue = Math::clamp(axisValue * axisDesc.sensitivity, -1.0f, 1.0f);
+			if(axisDesc.normalize)
+			{
+				if(isMouseAxis)
+				{
+					// Currently normalizing using value of 1, which isn't doing anything, but keep the code in case that
+					// changes
+					axisValue /= 1.0f;
+				}
+
+				axisValue = Math::clamp(axisValue * axisDesc.sensitivity, -1.0f, 1.0f);
+			}
+			else
+				axisValue *= axisDesc.sensitivity;
 
 			if (axisDesc.invert)
 				axisValue = -axisValue;

+ 7 - 7
Source/BansheeGLRenderAPI/BsGLTexture.cpp

@@ -24,7 +24,7 @@ namespace bs { namespace ct
 	}
 
 	GLTexture::~GLTexture()
-    { 
+	{
 		mSurfaceList.clear();
 		glDeleteTextures(1, &mTextureID);
 
@@ -285,18 +285,18 @@ namespace bs { namespace ct
 		{
 			for (UINT32 mip = 0; mip <= mProperties.getNumMipmaps(); mip++)
 			{
-                GLPixelBuffer *buf = bs_new<GLTextureBuffer>(getGLTextureTarget(), mTextureID, face, mip,
+				GLPixelBuffer *buf = bs_new<GLTextureBuffer>(getGLTextureTarget(), mTextureID, face, mip,
 					static_cast<GpuBufferUsage>(mProperties.getUsage()), mInternalFormat, mProperties.getNumSamples());
 
 				mSurfaceList.push_back(bs_shared_ptr<GLPixelBuffer>(buf));
-                if(buf->getWidth() == 0 || buf->getHeight() == 0 || buf->getDepth() == 0)
-                {
-					BS_EXCEPT(RenderingAPIException, 
-                        "Zero sized texture surface on texture face "
+				if(buf->getWidth() == 0 || buf->getHeight() == 0 || buf->getDepth() == 0)
+				{
+					BS_EXCEPT(RenderingAPIException,
+						"Zero sized texture surface on texture face "
 						+ toString(face) 
 						+ " mipmap "+toString(mip)
 						+ ". Probably, the GL driver refused to create the texture.");
-                }
+				}
 			}
 		}
 	}

+ 159 - 95
Source/BansheeGLRenderAPI/Linux/BsLinuxRenderWindow.cpp

@@ -9,6 +9,10 @@
 #include "Linux/BsLinuxContext.h"
 #include "BsGLPixelFormat.h"
 #include "BsGLRenderWindowManager.h"
+#include "Math/BsMath.h"
+
+#define XRANDR_ROTATION_LEFT    (1 << 1)
+#define XRANDR_ROTATION_RIGHT   (1 << 3)
 
 namespace bs
 {
@@ -63,7 +67,7 @@ namespace bs
 	{
 	LinuxRenderWindow::LinuxRenderWindow(const RENDER_WINDOW_DESC& desc, UINT32 windowId, LinuxGLSupport& glsupport)
 			: RenderWindow(desc, windowId), mWindow(nullptr), mGLSupport(glsupport), mContext(nullptr), mProperties(desc)
-			, mSyncedProperties(desc), mIsChild(false), mShowOnSwap(false), mOldScreenConfig(nullptr)
+			, mSyncedProperties(desc), mIsChild(false), mShowOnSwap(false)
 	{ }
 
 	LinuxRenderWindow::~LinuxRenderWindow()
@@ -72,12 +76,6 @@ namespace bs
 		{
 			LinuxPlatform::lockX();
 
-			if(mOldScreenConfig)
-			{
-				XRRFreeScreenConfigInfo(mOldScreenConfig);
-				mOldScreenConfig = nullptr;
-			}
-
 			mWindow->close();
 
 			bs_delete(mWindow);
@@ -106,6 +104,7 @@ namespace bs
 		windowDesc.height = mDesc.videoMode.getHeight();
 		windowDesc.title = mDesc.title;
 		windowDesc.showDecorations = !mDesc.toolWindow;
+		windowDesc.allowResize = true;
 		windowDesc.modal = mDesc.modal;
 		windowDesc.visualInfo = visualConfig.visualInfo;
 		windowDesc.screen = mDesc.videoMode.getOutputIdx();
@@ -114,6 +113,8 @@ namespace bs
 		opt = mDesc.platformSpecific.find("parentWindowHandle");
 		if (opt != mDesc.platformSpecific.end())
 			windowDesc.parent = (::Window)parseUINT64(opt->second);
+		else
+			windowDesc.parent = 0;
 
 		mIsChild = windowDesc.parent != 0;
 		props.isFullScreen = mDesc.fullscreen && !mIsChild;
@@ -162,126 +163,180 @@ namespace bs
 	{
 		THROW_IF_NOT_CORE_THREAD;
 
-		if (mIsChild)
+		VideoMode videoMode(width, height, refreshRate, monitorIdx);
+		setFullscreen(videoMode);
+	}
+
+	void LinuxRenderWindow::setVideoMode(INT32 screen, RROutput output, RRMode mode)
+	{
+		::Display* display = LinuxPlatform::getXDisplay();
+		::Window rootWindow = RootWindow(display, screen);
+
+		XRRScreenResources* screenRes = XRRGetScreenResources (display, rootWindow);
+		if(screenRes == nullptr)
+		{
+			LOGERR("XRR: Failed to retrieve screen resources. ");
 			return;
+		}
+
+		XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screenRes, output);
+		if(outputInfo == nullptr)
+		{
+			XRRFreeScreenResources(screenRes);
 
-		const LinuxVideoModeInfo& videoModeInfo = static_cast<const LinuxVideoModeInfo&>(RenderAPI::instance() .getVideoModeInfo());
-		UINT32 numOutputs = videoModeInfo.getNumOutputs();
-		if (numOutputs == 0)
+			LOGERR("XRR: Failed to retrieve output info for output: " + toString((UINT32)output));
 			return;
+		}
 
-		RenderWindowProperties& props = mProperties;
+		XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
+		if(crtcInfo == nullptr)
+		{
+			XRRFreeScreenResources(screenRes);
+			XRRFreeOutputInfo(outputInfo);
 
-		LinuxPlatform::lockX();
-		::Display* display = LinuxPlatform::getXDisplay();
+			LOGERR("XRR: Failed to retrieve CRTC info for output: " + toString((UINT32)output));
+			return;
+		}
+
+		// Note: This changes the user's desktop resolution permanently, even when the app exists, make sure to revert
+		// (Sadly there doesn't appear to be a better way)
+		Status status = XRRSetCrtcConfig (display, screenRes, outputInfo->crtc, CurrentTime,
+			crtcInfo->x, crtcInfo->y, mode, crtcInfo->rotation, &output, 1);
 
-		// Change video mode if required
-		bool changeVideoMode = false;
+		if(status != Success)
+			LOGERR("XRR: XRRSetCrtcConfig failed.");
 
-		XRRScreenConfiguration* screenConfig = XRRGetScreenInfo(display, XRootWindow(display, DefaultScreen (display)));
-		Rotation currentRotation;
-		SizeID currentSizeID = XRRConfigCurrentConfiguration(screenConfig, &currentRotation);
-		short currentRate = XRRConfigCurrentRate(screenConfig);
+		XRRFreeCrtcInfo(crtcInfo);
+		XRRFreeOutputInfo(outputInfo);
+		XRRFreeScreenResources(screenRes);
+	}
+
+	void LinuxRenderWindow::setFullscreen(const VideoMode& mode)
+	{
+		THROW_IF_NOT_CORE_THREAD;
 
-		int numSizes;
-		XRRScreenSize* screenSizes = XRRConfigSizes(screenConfig, &numSizes);
+		if (mIsChild)
+			return;
 
-		if((INT32)width != screenSizes[currentSizeID].width || (INT32)height != screenSizes[currentSizeID].height ||
-			currentRate != (short)refreshRate)
-			changeVideoMode = true;
+		const LinuxVideoModeInfo& videoModeInfo =
+				static_cast<const LinuxVideoModeInfo&>(RenderAPI::instance().getVideoModeInfo());
 
-		// If provided mode matches current mode, avoid making the video mode change
-		if(changeVideoMode)
+		UINT32 outputIdx = mode.getOutputIdx();
+		if(outputIdx >= videoModeInfo.getNumOutputs())
 		{
-			// Remember the old config so we can restore it when exiting fullscreen
-			if(mOldScreenConfig)
+			LOGERR("Invalid output device index.")
+			return;
+		}
+
+		const LinuxVideoOutputInfo& outputInfo =
+				static_cast<const LinuxVideoOutputInfo&>(videoModeInfo.getOutputInfo (outputIdx));
+
+		INT32 screen = outputInfo._getScreen();
+		RROutput outputID = outputInfo._getOutputID();
+
+		RRMode modeID = 0;
+		if(!mode.isCustom())
+		{
+			const LinuxVideoMode& videoMode = static_cast<const LinuxVideoMode&>(mode);
+			modeID = videoMode._getModeID();
+		}
+		else
+		{
+			LinuxPlatform::lockX();
+
+			// Look for mode matching the requested resolution
+			::Display* display = LinuxPlatform::getXDisplay();
+			::Window rootWindow = RootWindow(display, screen);
+
+			XRRScreenResources* screenRes = XRRGetScreenResources(display, rootWindow);
+			if (screenRes == nullptr)
+			{
+				LOGERR("XRR: Failed to retrieve screen resources. ");
+				return;
+			}
+
+			XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screenRes, outputID);
+			if (outputInfo == nullptr)
 			{
-				XRRFreeScreenConfigInfo(mOldScreenConfig);
-				mOldScreenConfig = nullptr;
+				XRRFreeScreenResources(screenRes);
+
+				LOGERR("XRR: Failed to retrieve output info for output: " + toString((UINT32)outputID));
+				return;
 			}
 
-			mOldScreenConfig = screenConfig;
-			mOldConfigSizeID = currentSizeID;
-			mOldConfigRate = currentRate;
+			XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
+			if (crtcInfo == nullptr)
+			{
+				XRRFreeScreenResources(screenRes);
+				XRRFreeOutputInfo(outputInfo);
+
+				LOGERR("XRR: Failed to retrieve CRTC info for output: " + toString((UINT32)outputID));
+				return;
+			}
 
-			// Look for size that best matches our requested video mode
-			bool foundSize = false;
-			SizeID foundSizeID = 0;
-			for(int i = 0; i < numSizes; i++)
+			bool foundMode = false;
+			for (INT32 i = 0; i < screenRes->nmode; i++)
 			{
-				UINT32 curWidth, curHeight;
-				if(currentRotation == RR_Rotate_90 || currentRotation == RR_Rotate_270)
+				const XRRModeInfo& modeInfo = screenRes->modes[i];
+
+				UINT32 width, height;
+
+				if (crtcInfo->rotation & (XRANDR_ROTATION_LEFT | XRANDR_ROTATION_RIGHT))
 				{
-					curWidth = (UINT32)screenSizes[i].height;
-					curHeight = (UINT32)screenSizes[i].width;
+					width = modeInfo.height;
+					height = modeInfo.width;
 				}
 				else
 				{
-					curWidth = (UINT32)screenSizes[i].width;
-					curHeight = (UINT32)screenSizes[i].height;
+					width = modeInfo.width;
+					height = modeInfo.height;
 				}
 
-				if(curWidth == width && curHeight == height)
+				float refreshRate;
+				if (modeInfo.hTotal != 0 && modeInfo.vTotal != 0)
+					refreshRate = (float) (modeInfo.dotClock / (double) (modeInfo.hTotal * modeInfo.vTotal));
+				else
+					refreshRate = 0.0f;
+
+				if (width == mode.getWidth() && height == mode.getHeight())
 				{
-					foundSizeID = (SizeID)i;
-					foundSize = true;
-					break;
+					modeID = modeInfo.id;
+					foundMode = true;
+
+					if (Math::approxEquals(refreshRate, mode.getRefreshRate()))
+						break;
 				}
 			}
 
-			if(!foundSize)
-				LOGERR("Cannot change video mode, requested resolution not supported.");
-
-			// Find refresh rate closest to the requested one, or fall back to 60
-			if(foundSize)
+			if (!foundMode)
 			{
-				int numRates;
-				short* rates = XRRConfigRates(screenConfig, foundSizeID, &numRates);
+				LinuxPlatform::unlockX();
 
-				short bestRate = 60;
-				for(int i = 0; i < numRates; i++)
-				{
-					if(rates[i] == (short)refreshRate)
-					{
-						bestRate = rates[i];
-						break;
-					}
-					else
-					{
-						short diffNew = (short)abs((short)refreshRate - rates[i]);
-						short diffOld = (short)abs((short)refreshRate - bestRate);
-
-						if(diffNew < diffOld)
-							bestRate = rates[i];
-					}
-				}
-
-				XRRSetScreenConfigAndRate(display, screenConfig, XRootWindow(display, DefaultScreen(display)),
-						foundSizeID, currentRotation, bestRate, CurrentTime);
+				LOGERR("Unable to enter fullscreen, unsupported video mode requested.");
+				return;
 			}
+
+			LinuxPlatform::unlockX();
 		}
 
+		LinuxPlatform::lockX();
+
+		setVideoMode(screen, outputID, modeID);
 		mWindow->_setFullscreen(true);
 
 		LinuxPlatform::unlockX();
 
+		RenderWindowProperties& props = mProperties;
 		props.isFullScreen = true;
 
 		props.top = 0;
 		props.left = 0;
-		props.width = width;
-		props.height = height;
+		props.width = mode.getWidth();
+		props.height = mode.getHeight();
 
 		_windowMovedOrResized();
 	}
 
-	void LinuxRenderWindow::setFullscreen(const VideoMode& mode)
-	{
-		THROW_IF_NOT_CORE_THREAD;
-
-		setFullscreen(mode.getWidth(), mode.getHeight(), mode.getRefreshRate(), mode.getOutputIdx());
-	}
-
 	void LinuxRenderWindow::setWindowed(UINT32 width, UINT32 height)
 	{
 		THROW_IF_NOT_CORE_THREAD;
@@ -291,24 +346,33 @@ namespace bs
 		if (!props.isFullScreen)
 			return;
 
-		props.isFullScreen = false;
-		props.width = width;
-		props.height = height;
-
-		LinuxPlatform::lockX();
-
 		// Restore old screen config
-		if(mOldScreenConfig)
+		const LinuxVideoModeInfo& videoModeInfo =
+				static_cast<const LinuxVideoModeInfo&>(RenderAPI::instance().getVideoModeInfo());
+
+		UINT32 outputIdx = 0; // 0 is always primary
+		if(outputIdx >= videoModeInfo.getNumOutputs())
 		{
-			::Display* display = LinuxPlatform::getXDisplay();
-			XRRSetScreenConfigAndRate(display, mOldScreenConfig, XRootWindow(display, DefaultScreen(display)),
-					mOldConfigSizeID, mOldConfigRotation, mOldConfigRate, CurrentTime);
-			XRRFreeScreenConfigInfo(mOldScreenConfig);
+			LOGERR("Invalid output device index.")
+			return;
 		}
+
+		const LinuxVideoOutputInfo& outputInfo =
+				static_cast<const LinuxVideoOutputInfo&>(videoModeInfo.getOutputInfo (outputIdx));
+
+		const LinuxVideoMode& desktopVideoMode = static_cast<const LinuxVideoMode&>(outputInfo.getDesktopVideoMode());
+
+		LinuxPlatform::lockX();
+
+		setVideoMode(outputInfo._getScreen(), outputInfo._getOutputID(), desktopVideoMode._getModeID());
 		mWindow->_setFullscreen(false);
 
 		LinuxPlatform::unlockX();
 
+		props.isFullScreen = false;
+		props.width = width;
+		props.height = height;
+
 		{
 			ScopedSpinLock lock(mLock);
 			mSyncedProperties.width = props.width;

+ 3 - 6
Source/BansheeGLRenderAPI/Linux/BsLinuxRenderWindow.h

@@ -130,6 +130,9 @@ namespace bs
 		protected:
 			friend class LinuxGLSupport;
 
+			/** Changes the video mode to the specified RandR mode on the specified output device. */
+			void setVideoMode(INT32 screen, RROutput output, RRMode mode);
+
 			/** @copydoc CoreObject::initialize */
 			void initialize() override;
 
@@ -152,12 +155,6 @@ namespace bs
 			RenderWindowProperties mSyncedProperties;
 			bool mIsChild;
 			bool mShowOnSwap;
-
-			// Config before entering fullscreen
-			XRRScreenConfiguration* mOldScreenConfig;
-			SizeID mOldConfigSizeID;
-			short mOldConfigRate;
-			Rotation mOldConfigRotation;
 		};
 	}
 

+ 21 - 12
Source/BansheeGLRenderAPI/Linux/BsLinuxVideoModeInfo.cpp

@@ -40,8 +40,8 @@ namespace bs { namespace ct
 				if(crtcInfo == nullptr)
 					continue;
 
-				VideoOutputInfo* output = bs_new<LinuxVideoOutputInfo>(display, outputInfo, crtcInfo, screenRes, j,
-						(UINT32)mOutputs.size());
+				VideoOutputInfo* output = bs_new<LinuxVideoOutputInfo>(display, i, outputInfo, crtcInfo, screenRes,
+						screenRes->outputs[j], (UINT32)mOutputs.size());
 
 				// Make sure the primary output is the first in the output list
 				if(i == defaultScreen && screenRes->outputs[j] == primaryOutput)
@@ -59,8 +59,9 @@ namespace bs { namespace ct
 		LinuxPlatform::unlockX();
 	}
 
-	LinuxVideoOutputInfo::LinuxVideoOutputInfo(::Display* x11Display, XRROutputInfo* outputInfo, XRRCrtcInfo* crtcInfo,
-		XRRScreenResources* screenRes, UINT32 resIdx, UINT32 outputIdx)
+	LinuxVideoOutputInfo::LinuxVideoOutputInfo(::Display* x11Display, INT32 screen, XRROutputInfo* outputInfo,
+		XRRCrtcInfo* crtcInfo, XRRScreenResources* screenRes, RROutput outputID, UINT32 outputIdx)
+			: mOutputID(outputID), mScreen(screen)
 	{
 		RRMode currentMode = crtcInfo->mode;
 
@@ -68,7 +69,7 @@ namespace bs { namespace ct
 		Atom EDID = XInternAtom(x11Display, "EDID", False);
 
 		INT32 numOutputProps;
-		Atom* outputProps = XRRListOutputProperties(x11Display, screenRes->outputs[resIdx], &numOutputProps);
+		Atom* outputProps = XRRListOutputProperties(x11Display, mOutputID, &numOutputProps);
 
 		for(INT32 k = 0; k < numOutputProps; k++)
 		{
@@ -80,9 +81,8 @@ namespace bs { namespace ct
 			INT32 actualFormat;
 			UINT8* data;
 
-			Status status = XRRGetOutputProperty(x11Display, screenRes->outputs[resIdx], outputProps[k], 0, 100, False,
-					False, AnyPropertyType, &actualType, &actualFormat, &numItems,
-					&bytesAfter, &data);
+			Status status = XRRGetOutputProperty(x11Display, mOutputID, outputProps[k], 0, 100, False,
+					False, AnyPropertyType, &actualType, &actualFormat, &numItems, &bytesAfter, &data);
 			if(status == Success)
 			{
 				// Decode EDID to get the name
@@ -148,7 +148,9 @@ namespace bs { namespace ct
 			else
 				refreshRate = 0.0f;
 
-			mVideoModes.push_back(bs_new<LinuxVideoMode>(width, height, refreshRate, outputIdx));
+			LinuxVideoMode* videoMode = new (bs_alloc<LinuxVideoMode>())
+					LinuxVideoMode(width, height, refreshRate, outputIdx, modeInfo.id);
+			mVideoModes.push_back(videoMode);
 		}
 
 		// Save current desktop mode
@@ -156,14 +158,21 @@ namespace bs { namespace ct
 		{
 			if(screenRes->modes[k].id == currentMode)
 			{
-				mDesktopVideoMode = bs_new<LinuxVideoMode>(mVideoModes[k]->getWidth(), mVideoModes[k]->getHeight(),
-						mVideoModes[k]->getRefreshRate(), mVideoModes[k]->getOutputIdx());
+				mDesktopVideoMode = new (bs_alloc<LinuxVideoMode>())
+						LinuxVideoMode(mVideoModes[k]->getWidth(), mVideoModes[k]->getHeight(),
+						mVideoModes[k]->getRefreshRate(), mVideoModes[k]->getOutputIdx(), currentMode);
 				break;
 			}
 		}
 	}
 
 	LinuxVideoMode::LinuxVideoMode(UINT32 width, UINT32 height, float refreshRate, UINT32 outputIdx)
-			:VideoMode(width, height, refreshRate, outputIdx)
+		:VideoMode(width, height, refreshRate, outputIdx), mModeID((RRMode)-1)
 	{ }
+
+	LinuxVideoMode::LinuxVideoMode(UINT32 width, UINT32 height, float refreshRate, UINT32 outputIdx, RRMode modeID)
+		:VideoMode(width, height, refreshRate, outputIdx), mModeID(modeID)
+	{
+		mIsCustom = false;
+	}
 }}

+ 17 - 2
Source/BansheeGLRenderAPI/Linux/BsLinuxVideoModeInfo.h

@@ -18,16 +18,31 @@ namespace bs { namespace ct
 	public:
 		LinuxVideoMode(UINT32 width, UINT32 height, float refreshRate, UINT32 outputIdx);
 
+		/** Returns internal RandR video mode id. */
+		RRMode _getModeID() const { return mModeID; }
+
 	private:
+		LinuxVideoMode(UINT32 width, UINT32 height, float refreshRate, UINT32 outputIdx, RRMode modeID);
 		friend class LinuxVideoOutputInfo;
+
+		RRMode mModeID;
 	};
 
 	/** @copydoc VideoOutputInfo */
 	class LinuxVideoOutputInfo : public VideoOutputInfo
 	{
 	public:
-		LinuxVideoOutputInfo(::Display* x11Display, XRROutputInfo* outputInfo, XRRCrtcInfo* crtcInfo,
-			 XRRScreenResources* screenRes, UINT32 resIdx, UINT32 outputIdx);
+		LinuxVideoOutputInfo(::Display* x11Display, INT32 screen, XRROutputInfo* outputInfo, XRRCrtcInfo* crtcInfo,
+			 XRRScreenResources* screenRes, RROutput outputID, UINT32 outputIdx);
+
+		/** Returns internal RandR output device id. */
+		RROutput _getOutputID() const { return mOutputID; }
+
+		/** Returns X11 screen this output renders to. One screen can contain multiple output devices. */
+		INT32 _getScreen() const { return mScreen;}
+	private:
+		RROutput mOutputID;
+		INT32 mScreen;
 	};
 
 	/** @copydoc VideoModeInfo */

+ 2 - 2
Source/BansheeUtility/Threading/BsTaskScheduler.cpp

@@ -7,8 +7,8 @@ namespace bs
 {
 	Task::Task(const PrivatelyConstruct& dummy, const String& name, std::function<void()> taskWorker, 
 		TaskPriority priority, SPtr<Task> dependency)
-		:mName(name), mPriority(priority), mTaskId(0), mTaskWorker(taskWorker), mTaskDependency(dependency),
-		mParent(nullptr)
+		: mName(name), mPriority(priority), mTaskId(0), mTaskWorker(taskWorker), mTaskDependency(dependency), mState(0)
+		, mParent(nullptr)
 	{
 
 	}