427 lines
10 KiB
C++
427 lines
10 KiB
C++
#include <X11/Xlib.h>
|
|
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <vector>
|
|
#include <cstring>
|
|
#include <sys/capability.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <float.h>
|
|
|
|
#include "main.hpp"
|
|
#include "steamcompmgr.hpp"
|
|
#include "drm.hpp"
|
|
#include "rendervulkan.hpp"
|
|
#include "sdlwindow.hpp"
|
|
#include "wlserver.hpp"
|
|
#include "gpuvis_trace_utils.h"
|
|
|
|
#if HAVE_PIPEWIRE
|
|
#include "pipewire.hpp"
|
|
#endif
|
|
|
|
const char *gamescope_optstring = nullptr;
|
|
|
|
const struct option *gamescope_options = (struct option[]){
|
|
{ "help", no_argument, nullptr, 0 },
|
|
{ "nested-width", required_argument, nullptr, 'w' },
|
|
{ "nested-height", required_argument, nullptr, 'h' },
|
|
{ "nested-refresh", required_argument, nullptr, 'r' },
|
|
{ "max-scale", required_argument, nullptr, 'm' },
|
|
{ "integer-scale", no_argument, nullptr, 'i' },
|
|
{ "output-width", required_argument, nullptr, 'W' },
|
|
{ "output-height", required_argument, nullptr, 'H' },
|
|
{ "nearest-neighbor-filter", no_argument, nullptr, 'n' },
|
|
|
|
// nested mode options
|
|
{ "nested-unfocused-refresh", required_argument, nullptr, 'o' },
|
|
{ "borderless", no_argument, nullptr, 'b' },
|
|
{ "fullscreen", no_argument, nullptr, 'f' },
|
|
|
|
// embedded mode options
|
|
{ "disable-layers", no_argument, nullptr, 0 },
|
|
{ "debug-layers", no_argument, nullptr, 0 },
|
|
{ "prefer-output", required_argument, nullptr, 'O' },
|
|
|
|
// steamcompmgr options
|
|
{ "cursor", required_argument, nullptr, 0 },
|
|
{ "ready-fd", required_argument, nullptr, 'R' },
|
|
{ "stats-path", required_argument, nullptr, 'T' },
|
|
{ "hide-cursor-delay", required_argument, nullptr, 'C' },
|
|
{ "debug-focus", no_argument, nullptr, 0 },
|
|
{ "synchronous-x11", no_argument, nullptr, 0 },
|
|
{ "debug-hud", no_argument, nullptr, 'v' },
|
|
{ "debug-events", no_argument, nullptr, 0 },
|
|
{ "steam", no_argument, nullptr, 'e' },
|
|
{ "force-composition", no_argument, nullptr, 'c' },
|
|
{ "disable-xres", no_argument, nullptr, 'x' },
|
|
|
|
{} // keep last
|
|
};
|
|
|
|
const char usage[] =
|
|
"usage: gamescope [options...] -- [command...]\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" --help show help message\n"
|
|
" -w, --nested-width game width\n"
|
|
" -h, --nexted-height game height\n"
|
|
" -r, --nested-refresh game refresh rate (frames per second)\n"
|
|
" -m, --max-scale maximum scale factor\n"
|
|
" -i, --integer-scale force scale factor to integer\n"
|
|
" -W, --output-width output width\n"
|
|
" -H, --output-height output height\n"
|
|
" -n, --nearest-neighbor-filter use nearest neighbor filtering\n"
|
|
" --cursor path to default cursor image\n"
|
|
" -R, --ready-fd notify FD when ready\n"
|
|
" -T, --stats-path write statistics to path\n"
|
|
" -C, --hide-cursor-delay hide cursor image after delay\n"
|
|
" -e, --steam enable Steam integration\n"
|
|
"\n"
|
|
"Nested mode options:\n"
|
|
" -o, --nested-unfocused-refresh game refresh rate when unfocused\n"
|
|
" -b, --borderless make the window borderless\n"
|
|
" -f, --fullscreen make the window fullscreen\n"
|
|
"\n"
|
|
"Embedded mode options:\n"
|
|
" -O, --prefer-output list of connectors in order of preference\n"
|
|
"\n"
|
|
"Debug options:\n"
|
|
" --disable-layers disable libliftoff (hardware planes)\n"
|
|
" --debug-layers debug libliftoff\n"
|
|
" --debug-focus debug XWM focus\n"
|
|
" --synchronous-x11 force X11 connection synchronization\n"
|
|
" --debug-hud paint HUD with debug info\n"
|
|
" --debug-events debug X11 events\n"
|
|
" --force-composition disable direct scan-out\n"
|
|
" --disable-xres disable XRes for PID lookup\n"
|
|
"\n"
|
|
"Keyboard shortcuts:\n"
|
|
" Super + F toggle fullscreen\n"
|
|
" Super + N toggle nearest neighbour filtering\n"
|
|
" Super + S take a screenshot\n"
|
|
"";
|
|
|
|
std::atomic< bool > g_bRun{true};
|
|
|
|
int g_nNestedWidth = 0;
|
|
int g_nNestedHeight = 0;
|
|
int g_nNestedRefresh = 0;
|
|
int g_nNestedUnfocusedRefresh = 0;
|
|
|
|
uint32_t g_nOutputWidth = 0;
|
|
uint32_t g_nOutputHeight = 0;
|
|
int g_nOutputRefresh = 60;
|
|
|
|
bool g_bFullscreen = false;
|
|
|
|
bool g_bIsNested = false;
|
|
|
|
bool g_bFilterGameWindow = true;
|
|
|
|
bool g_bBorderlessOutputWindow = false;
|
|
|
|
bool g_bNiceCap = false;
|
|
int g_nOldNice = 0;
|
|
int g_nNewNice = 0;
|
|
|
|
float g_flMaxWindowScale = FLT_MAX;
|
|
bool g_bIntegerScale = false;
|
|
|
|
pthread_t g_mainThread;
|
|
|
|
int BIsNested()
|
|
{
|
|
return g_bIsNested == true;
|
|
}
|
|
|
|
static int initOutput(void);
|
|
static void steamCompMgrThreadRun(int argc, char **argv);
|
|
|
|
static std::string build_optstring(const struct option *options)
|
|
{
|
|
std::string optstring;
|
|
for (size_t i = 0; options[i].name != nullptr; i++) {
|
|
if (!options[i].name)
|
|
continue;
|
|
|
|
char str[] = { (char) options[i].val, '\0' };
|
|
optstring.append(str);
|
|
|
|
if (options[i].val && options[i].has_arg)
|
|
optstring.append(":");
|
|
}
|
|
return optstring;
|
|
}
|
|
|
|
static void handle_signal( int sig )
|
|
{
|
|
switch ( sig ) {
|
|
case SIGUSR2:
|
|
take_screenshot();
|
|
break;
|
|
case SIGTERM:
|
|
case SIGINT:
|
|
fprintf( stderr, "gamescope: received kill signal, terminating!\n" );
|
|
g_bRun = false;
|
|
break;
|
|
default:
|
|
assert( false ); // unreachable
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
static std::string optstring = build_optstring(gamescope_options);
|
|
gamescope_optstring = optstring.c_str();
|
|
|
|
int o;
|
|
int opt_index = -1;
|
|
while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1)
|
|
{
|
|
const char *opt_name;
|
|
switch (o) {
|
|
case 'w':
|
|
g_nNestedWidth = atoi( optarg );
|
|
break;
|
|
case 'h':
|
|
g_nNestedHeight = atoi( optarg );
|
|
break;
|
|
case 'W':
|
|
g_nOutputWidth = atoi( optarg );
|
|
break;
|
|
case 'H':
|
|
g_nOutputHeight = atoi( optarg );
|
|
break;
|
|
case 'r':
|
|
g_nNestedRefresh = atoi( optarg );
|
|
break;
|
|
case 'o':
|
|
g_nNestedUnfocusedRefresh = atoi( optarg );
|
|
break;
|
|
case 'm':
|
|
g_flMaxWindowScale = atof( optarg );
|
|
break;
|
|
case 'i':
|
|
g_bIntegerScale = true;
|
|
break;
|
|
case 'n':
|
|
g_bFilterGameWindow = false;
|
|
break;
|
|
case 'b':
|
|
g_bBorderlessOutputWindow = true;
|
|
break;
|
|
case 'f':
|
|
g_bFullscreen = true;
|
|
break;
|
|
case 'O':
|
|
g_sOutputName = optarg;
|
|
break;
|
|
case 0: // long options without a short option
|
|
opt_name = gamescope_options[opt_index].name;
|
|
if (strcmp(opt_name, "help") == 0) {
|
|
fprintf(stderr, "%s", usage);
|
|
return 0;
|
|
} else if (strcmp(opt_name, "disable-layers") == 0) {
|
|
g_bUseLayers = false;
|
|
} else if (strcmp(opt_name, "debug-layers") == 0) {
|
|
g_bDebugLayers = true;
|
|
}
|
|
break;
|
|
case '?':
|
|
fprintf( stderr, "Unknown option\n" );
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if ( g_nOutputHeight == 0 )
|
|
{
|
|
if ( g_nOutputWidth != 0 )
|
|
{
|
|
fprintf( stderr, "Cannot specify -W without -H\n" );
|
|
return 1;
|
|
}
|
|
g_nOutputHeight = 720;
|
|
}
|
|
if ( g_nOutputWidth == 0 )
|
|
g_nOutputWidth = g_nOutputHeight * 16 / 9;
|
|
|
|
cap_t caps;
|
|
caps = cap_get_proc();
|
|
cap_flag_value_t nicecapvalue = CAP_CLEAR;
|
|
|
|
if ( caps != nullptr )
|
|
{
|
|
cap_get_flag( caps, CAP_SYS_NICE, CAP_EFFECTIVE, &nicecapvalue );
|
|
|
|
if ( nicecapvalue == CAP_SET )
|
|
{
|
|
g_bNiceCap = true;
|
|
|
|
errno = 0;
|
|
int nOldNice = nice( 0 );
|
|
if ( nOldNice != -1 && errno == 0 )
|
|
{
|
|
g_nOldNice = nOldNice;
|
|
}
|
|
|
|
errno = 0;
|
|
int nNewNice = nice( -20 );
|
|
if ( nNewNice != -1 && errno == 0 )
|
|
{
|
|
g_nNewNice = nNewNice;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( g_bNiceCap == false )
|
|
{
|
|
fprintf( stderr, "No CAP_SYS_NICE, falling back to regular-priority compute and threads.\nPerformance will be affected.\n" );
|
|
}
|
|
|
|
if ( gpuvis_trace_init() != -1 )
|
|
{
|
|
fprintf( stderr, "Tracing is enabled\n");
|
|
}
|
|
|
|
XInitThreads();
|
|
g_mainThread = pthread_self();
|
|
|
|
if ( getenv("DISPLAY") != NULL || getenv("WAYLAND_DISPLAY") != NULL )
|
|
{
|
|
g_bIsNested = true;
|
|
}
|
|
|
|
if ( wlsession_init() != 0 )
|
|
{
|
|
fprintf( stderr, "Failed to initialize Wayland session\n" );
|
|
return 1;
|
|
}
|
|
|
|
if ( initOutput() != 0 )
|
|
{
|
|
fprintf( stderr, "Failed to initialize output\n" );
|
|
return 1;
|
|
}
|
|
|
|
if ( !vulkan_init() )
|
|
{
|
|
fprintf( stderr, "Failed to initialize Vulkan\n" );
|
|
return 1;
|
|
}
|
|
|
|
// TODO: get the DRM device from the Vulkan device
|
|
if ( g_bIsNested == false && g_vulkanHasDrmDevId )
|
|
{
|
|
struct stat drmStat = {};
|
|
if ( fstat( g_DRM.fd, &drmStat ) != 0 )
|
|
{
|
|
perror( "fstat failed on DRM FD" );
|
|
return 1;
|
|
}
|
|
|
|
if ( drmStat.st_rdev != g_vulkanDrmDevId )
|
|
{
|
|
fprintf( stderr, "Mismatch between DRM and Vulkan devices\n" );
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Prevent our clients from connecting to the parent compositor
|
|
unsetenv("WAYLAND_DISPLAY");
|
|
|
|
// If DRM format modifiers aren't supported, prevent our clients from using
|
|
// DCC, as this can cause tiling artifacts.
|
|
if ( !g_vulkanSupportsModifiers )
|
|
{
|
|
const char *pchR600Debug = getenv( "R600_DEBUG" );
|
|
|
|
if ( pchR600Debug == nullptr )
|
|
{
|
|
setenv( "R600_DEBUG", "nodcc", 1 );
|
|
}
|
|
else if ( strstr( pchR600Debug, "nodcc" ) == nullptr )
|
|
{
|
|
std::string strPreviousR600Debug = pchR600Debug;
|
|
strPreviousR600Debug.append( ",nodcc" );
|
|
setenv( "R600_DEBUG", strPreviousR600Debug.c_str(), 1 );
|
|
}
|
|
}
|
|
|
|
if ( g_nNestedHeight == 0 )
|
|
{
|
|
if ( g_nNestedWidth != 0 )
|
|
{
|
|
fprintf( stderr, "Cannot specify -w without -h\n" );
|
|
return 1;
|
|
}
|
|
g_nNestedWidth = g_nOutputWidth;
|
|
g_nNestedHeight = g_nOutputHeight;
|
|
}
|
|
if ( g_nNestedWidth == 0 )
|
|
g_nNestedWidth = g_nNestedHeight * 16 / 9;
|
|
|
|
if ( wlserver_init(argc, argv, g_bIsNested == true ) != 0 )
|
|
{
|
|
fprintf( stderr, "Failed to initialize wlserver\n" );
|
|
return 1;
|
|
}
|
|
|
|
setenv("DISPLAY", wlserver_get_nested_display_name(), 1);
|
|
setenv("GAMESCOPE_WAYLAND_DISPLAY", wlserver_get_wl_display_name(), 1);
|
|
|
|
#if HAVE_PIPEWIRE
|
|
if ( !init_pipewire() )
|
|
{
|
|
fprintf( stderr, "Warning: failed to setup PipeWire, screen capture won't be available\n" );
|
|
}
|
|
#endif
|
|
|
|
std::thread steamCompMgrThread( steamCompMgrThreadRun, argc, argv );
|
|
|
|
signal( SIGTERM, handle_signal );
|
|
signal( SIGINT, handle_signal );
|
|
signal( SIGUSR2, handle_signal );
|
|
|
|
wlserver_run();
|
|
|
|
steamCompMgrThread.join();
|
|
}
|
|
|
|
static void steamCompMgrThreadRun(int argc, char **argv)
|
|
{
|
|
steamcompmgr_main( argc, argv );
|
|
|
|
pthread_kill( g_mainThread, SIGINT );
|
|
}
|
|
|
|
static int initOutput(void)
|
|
{
|
|
if ( g_bIsNested == true )
|
|
{
|
|
return sdlwindow_init() == false;
|
|
}
|
|
else
|
|
{
|
|
return init_drm( &g_DRM, nullptr );
|
|
}
|
|
}
|
|
|
|
void wayland_commit(struct wlr_surface *surf, struct wlr_buffer *buf)
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock( wayland_commit_lock );
|
|
|
|
ResListEntry_t newEntry = {
|
|
.surf = surf,
|
|
.buf = buf,
|
|
};
|
|
wayland_commit_queue.push_back( newEntry );
|
|
}
|
|
|
|
nudge_steamcompmgr();
|
|
}
|