I'm working on a Unity project in * Linux where I need to display the game/project on 2 screens using only 1 camera and 1 "display" , for better performance. What can i do to solve this problem? I will need set or modify something in the OS? Can i do it programmatically?
I'm using:
Edit:
For a exemple: I want to "combine" both monitors/display to act as one. So when i have two screen of 1920x1080 the game need to "extend" the window and work as if the screen was 1920x2160.
Current i tried to used the follow command in unity:
Screen.SetResolution(int width, int height, FullScreenMode
fullscreenMode);
with:
In all the modes the game stayed in only one screen, liming it self to 1920x1080.
In Unity 2020.3.16f1, I couldn't find any direct solution for Linux (Debian).
The solution I found is to meddle with the X windows server, Xorg has been used as default since Debian 4.0.
The option I used: Xorg has a module called Xrandr (Linux users probably already know that) that provides the xrandr commands in terminal and interacts with the configuration of the displays.
To check what displays are connected I use the command xrandr --listmonitors | grep '+' | awk {'print $4'}
xrandr --listmonitors | grep '+' | awk {'print $4'}
xrandr --listmonitors | grep '+' | awk {'print $4'}
. For me it return:
So to combine the display use the command xrandr --setmonitor CustomName auto HDMI-0,HDMI-1}
, this create a virtual display.
As I want to set one screen above the other I use this xrandr --output HDMI-1 --above HDMI-0
.
Now both display work as one.
I want Unity to call these commands, so that when the game opens it sets the configuration automatically, but because I am using the IL2CPP Scripting Backend I can't use Application.Process to call the commands. To solve that I create a lib in C++ 11 , libSystemCommands.so , to execute the commands.
system_commands.cpp
#include "system_commands.h"
#include <vector>
#include <array>
#include <memory>
#include <cstring>
void system_command(const char * command){
std::system(command);
}
char* system_command_with_output(const char * command){
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE , decltype(&pclose)> pipe(popen(command,"r"), pclose);
if (!pipe) throw std::runtime_error("popopen() failed!");
while (fgets(buffer.data(),buffer.size(), pipe.get())!= nullptr)
result += buffer.data();
char * res = (char *) malloc(strlen(result.c_str())+1);
strcpy(res,result.c_str());
return res;
}
system_commands.h
#ifndef SYSTEMCOMMAND_LIBRARY_H
#define SYSTEMCOMMAND_LIBRARY_H
#if defined(_MSC_VER)
#ifdef NativePlugin_EXPORTS
#define NATIVEPLUGIN_API extern "C" __declspec(dllexport)
#else
#define NATIVEPLUGIN_API extern "C" __declspec(dllimport)
#endif
#else
#define NATIVEPLUGIN_API extern "C"
#endif
NATIVEPLUGIN_API void system_command(const char * command);
NATIVEPLUGIN_API char* system_command_with_output(const char * command);
#endif //SYSTEMCOMMAND_LIBRARY_H
In Unity I create the following script to control the changes:
DisplayManager.cs
using System;
using System.Runtime.InteropServices;
public static class DisplayManager
{
//List of displays/monitors connected
private static string[] _displays;
private static string[] _originalPositions;
//Detect the displays/monitors connected, using xrandr
public static void DetectDisplays()
{
//Get and set the display connected, to a list.
string result = system_command_with_output("xrandr --listmonitors | grep '+' | awk {'print $4'}");
_displays = result.Split(new string[] { Environment.NewLine },
StringSplitOptions.RemoveEmptyEntries);
// We want to run the rest only if there is two or more screens.
if (_displays.Length<=1) return;
// Get the original position of the two first display/monitors.
result = system_command_with_output(
"xrandr --listmonitors | grep '+' | awk '{print $3}' | awk -F'+' '{print $2,$3}'");
result = result.Replace(' ', 'x');
_originalPositions = result.Split(new string[] { Environment.NewLine },
StringSplitOptions.RemoveEmptyEntries);
}
//Combine two displays/monitors in one using xrandr
public static void CombineDisplays()
{
// We want to run this only if there is two or more screens.
if (_displays.Length<=1) return;
// xrandr command to create a virtual monitor combining others monitors.
system_command($"xrandr --setmonitor DisplayCombination auto {_displays[0]},{_displays[1]}");
// xrandr don´t apply the new virtual monitor automatically and don´t set a change if the input
// is equal of the current state. So the two command is to make sure to apple de combination and
// the display is one above another.
system_command($"xrandr --auto & xrandr --output {_displays[1]} --left-of {_displays[0]}");
// Set the desired position.
system_command($"xrandr --auto & xrandr --output {_displays[1]} --above {_displays[0]}");
}
//Reset the Display to before the Combination.
public static void ResetDisplays()
{
// Delete the virtual display created, if it exist
system_command("xrandr --delmonitor DisplayCombination");
// We want to run this only if there is two or more screens.
if (_displays.Length<=1) return;
// xrandr don´t apply the deletion of the virtual display automatically and don´t set a change
// if the input is equal of the current state. So the two command is to make sure to apple de
// deletion and reset the displays/monitors to the original position.
system_command($"xrandr --auto & xrandr --output {_displays[1]} --left-of {_displays[0]}");
//Set the Displays to their original position.
for(int i = 0; i <_displays.Length;i++)
system_command($"xrandr --output {_displays[i]} --pos {_originalPositions[i]}");
}
// function from the lib that intermediate the command execution with the system.
[DllImport("SystemCommands")]
private static extern void system_command(string command);
// function from the lib that intermediate the command execution with the system, with output.
[DllImport("SystemCommands")]
private static extern string system_command_with_output(string command);
}
And then in another script I only need to call this method and set the Screen.SetResolution
to do the trick.
Example:
private int _newWidth;
private int _newHeight;
private void Awake()
{
_newWidth = Display.main.systemWidth;
_newHeight = Display.main.systemHeight;
if (Display.displays.Length > 1)
_newHeight += Display.displays[1].systemHeight;
DisplayManager.DetectDisplays();
DisplayManager.CombineDisplays();
}
private void Start()
{
Screen.SetResolution(_newWidth,_newHeight,FullScreenMode.FullScreenWindow);
}
private void OnApplicationQuit()
{
DisplayManager.ResetDisplays();
}
The other option (I didn't check): Is using the Xorg configuration folder, that is read when you login in account, together with the module Xinerama (I need to check if Xinerama is really needed).
Create a file at Xorg.conf.d , as ##-name.conf (“##” being a number identifies the priority of execution/reading, and “name” being something you can use to identify it more easily).
The folder can be at:
And you will need to setup a SERVERLAYOUT , and enable the option Xinerama.
Section "ServerLayout"
Identifier "name"
Screen "screen−id"
...
Option "Xinerama" "1"
...
EndSection
More information abou xorg at: https://www.x.org/releases/current/doc/man/man5/xorg.conf.5.xhtml#heading16 https://wiki.archlinux.org/title/multihead
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.