mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-04-05 11:06:40 +09:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f32d56f21 | ||
|
|
b9537941c9 | ||
|
|
2290f0641c | ||
|
|
f9e93467b2 | ||
|
|
7d7f986668 | ||
|
|
1b957d5b40 | ||
|
|
8ad6a1f4cb | ||
|
|
0cf0a9abf5 | ||
|
|
bc58e382b7 | ||
|
|
630c504241 | ||
|
|
0528bc6f31 | ||
|
|
a8b3e38082 | ||
|
|
d0a620a574 | ||
|
|
ca0be79344 | ||
|
|
53557133c2 | ||
|
|
7f00ce22e7 | ||
|
|
3ba1dbda98 | ||
|
|
a849d2cdad |
@@ -70,6 +70,7 @@ Here are some known unofficial packages:
|
||||
* Fedora: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/)
|
||||
* OpenMandriva: [gpu-screen-recorder](https://github.com/OpenMandrivaAssociation/gpu-screen-recorder)
|
||||
* Solus: [gpu-screen-recorder](https://github.com/getsolus/packages/tree/main/packages/g/gpu-screen-recorder)
|
||||
* Nobara: [Nobara wiki](https://wiki.nobaraproject.org/en/general-usage/additional-software/GPU-Screen-Recorder)
|
||||
|
||||
# Dependencies
|
||||
GPU Screen Recorder uses meson build system so you need to install `meson` to build GPU Screen Recorder.
|
||||
@@ -77,7 +78,6 @@ GPU Screen Recorder uses meson build system so you need to install `meson` to bu
|
||||
## Build dependencies
|
||||
These are the dependencies needed to build GPU Screen Recorder:
|
||||
|
||||
* libglvnd (which provides libgl, libglx and libegl)
|
||||
* vulkan-headers
|
||||
* ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)
|
||||
* x11 (libx11, libxcomposite, libxrandr, libxfixes, libxdamage)
|
||||
@@ -88,6 +88,8 @@ These are the dependencies needed to build GPU Screen Recorder:
|
||||
* wayland (wayland-client, wayland-egl, wayland-scanner)
|
||||
|
||||
## Runtime dependencies
|
||||
* libglvnd (which provides libgl, libglx and libegl) is needed. Your system needs to support at least OpenGL ES 3.0 (released in 2012)
|
||||
|
||||
There are also additional dependencies needed at runtime depending on your GPU vendor:
|
||||
|
||||
### AMD
|
||||
@@ -96,7 +98,7 @@ There are also additional dependencies needed at runtime depending on your GPU v
|
||||
|
||||
### Intel
|
||||
* mesa
|
||||
* vaapi (intel-media-driver/libva-intel-driver/linux-firmware, depending on which intel iGPU you have)
|
||||
* vaapi (intel-media-driver/libva-intel-driver/linux-firmware-intel, depending on which intel iGPU you have)
|
||||
|
||||
### NVIDIA
|
||||
* cuda runtime (libcuda.so.1) (libnvidia-compute)
|
||||
|
||||
4
TODO
4
TODO
@@ -302,3 +302,7 @@ Allow medium, high, very_high and ultra quality for -bm cbr. If that is used the
|
||||
Maybe do this in the ui instead (or both?), to show estimated file size.
|
||||
|
||||
Maybe remove shader compute code. It doesn't seem necessary anymore now that glSwapBuffer/glFinish isn't used. dbus server isn't needed anymore either, the code can be moved back to the gpu screen recorder process.
|
||||
|
||||
Add proper check if opengl functions are supported. dlsym for the symbol will return a no-op function if it's not supported, so it silently fails if used.
|
||||
|
||||
Colors are offset to bottom left by 1 pixel or so on steam deck in landscape mode.
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
#include "dbus_client.h"
|
||||
#include "../protocol.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
// TODO: Error checking for write/read
|
||||
|
||||
static bool gsr_dbus_client_wait_for_startup(gsr_dbus_client *self) {
|
||||
struct pollfd poll_fd = {
|
||||
.fd = self->socket_pair[0],
|
||||
.events = POLLIN,
|
||||
.revents = 0
|
||||
};
|
||||
for(;;) {
|
||||
int poll_res = poll(&poll_fd, 1, 100);
|
||||
if(poll_res > 0 && (poll_fd.revents & POLLIN)) {
|
||||
char msg;
|
||||
read(self->socket_pair[0], &msg, 1);
|
||||
return true;
|
||||
} else {
|
||||
int status = 0;
|
||||
int wait_result = waitpid(self->pid, &status, WNOHANG);
|
||||
if(wait_result != 0) {
|
||||
int exit_code = -1;
|
||||
if(WIFEXITED(status))
|
||||
exit_code = WEXITSTATUS(status);
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_init: server died or never started, exit code: %d\n", exit_code);
|
||||
self->pid = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool gsr_dbus_client_init(gsr_dbus_client *self, const char *screencast_restore_token) {
|
||||
memset(self, 0, sizeof(*self));
|
||||
|
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, self->socket_pair) == -1) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_init: socketpair failed, error: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(screencast_restore_token) {
|
||||
self->screencast_restore_token = strdup(screencast_restore_token);
|
||||
if(!self->screencast_restore_token) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_init: failed to clone restore token\n");
|
||||
gsr_dbus_client_deinit(self);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self->pid = fork();
|
||||
if(self->pid == -1) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_init: failed to fork process\n");
|
||||
gsr_dbus_client_deinit(self);
|
||||
return false;
|
||||
} else if(self->pid == 0) { /* child */
|
||||
char socket_pair_server_str[32];
|
||||
snprintf(socket_pair_server_str, sizeof(socket_pair_server_str), "%d", self->socket_pair[1]);
|
||||
|
||||
/* Needed for NixOS for example, to make sure gsr-dbus-server doesn't inherit cap_sys_nice */
|
||||
prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
|
||||
|
||||
const char *args[] = { "gsr-dbus-server", socket_pair_server_str, self->screencast_restore_token ? self->screencast_restore_token : "", NULL };
|
||||
execvp(args[0], (char *const*)args);
|
||||
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_init: failed to launch \"gsr-dbus-server\", error: %s\n", strerror(errno));
|
||||
_exit(127);
|
||||
} else { /* parent */
|
||||
if(!gsr_dbus_client_wait_for_startup(self)) {
|
||||
gsr_dbus_client_deinit(self);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gsr_dbus_client_deinit(gsr_dbus_client *self) {
|
||||
for(int i = 0; i < 2; ++i) {
|
||||
if(self->socket_pair[i] > 0) {
|
||||
close(self->socket_pair[i]);
|
||||
self->socket_pair[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(self->screencast_restore_token) {
|
||||
free(self->screencast_restore_token);
|
||||
self->screencast_restore_token = NULL;
|
||||
}
|
||||
|
||||
if(self->pid > 0) {
|
||||
kill(self->pid, SIGKILL);
|
||||
int status = 0;
|
||||
waitpid(self->pid, &status, 0);
|
||||
self->pid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int gsr_dbus_client_screencast_create_session(gsr_dbus_client *self, char *session_handle, size_t session_handle_size) {
|
||||
const gsr_dbus_request_message request = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_REQ_CREATE_SESSION,
|
||||
.create_session = (gsr_dbus_message_req_create_session) {}
|
||||
};
|
||||
write(self->socket_pair[0], &request, sizeof(request));
|
||||
|
||||
gsr_dbus_response_message response = {0};
|
||||
read(self->socket_pair[0], &response, sizeof(response));
|
||||
|
||||
if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_create_session: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_create_session: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code);
|
||||
return response.error.error_code;
|
||||
}
|
||||
|
||||
if(response.type != GSR_DBUS_MESSAGE_RESP_CREATE_SESSION) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_create_session: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_CREATE_SESSION, response.type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(session_handle, session_handle_size, "%s", response.create_session.session_handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gsr_dbus_client_screencast_select_sources(gsr_dbus_client *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode) {
|
||||
gsr_dbus_request_message request = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_REQ_SELECT_SOURCES,
|
||||
.select_sources = (gsr_dbus_message_req_select_sources) {
|
||||
.capture_type = capture_type,
|
||||
.cursor_mode = cursor_mode
|
||||
}
|
||||
};
|
||||
snprintf(request.select_sources.session_handle, sizeof(request.select_sources.session_handle), "%s", session_handle);
|
||||
write(self->socket_pair[0], &request, sizeof(request));
|
||||
|
||||
gsr_dbus_response_message response = {0};
|
||||
read(self->socket_pair[0], &response, sizeof(response));
|
||||
|
||||
if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_select_sources: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_select_sources: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code);
|
||||
return response.error.error_code;
|
||||
}
|
||||
|
||||
if(response.type != GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_select_sources: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES, response.type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gsr_dbus_client_screencast_start(gsr_dbus_client *self, const char *session_handle, uint32_t *pipewire_node) {
|
||||
*pipewire_node = 0;
|
||||
|
||||
gsr_dbus_request_message request = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_REQ_START,
|
||||
.start = (gsr_dbus_message_req_start) {}
|
||||
};
|
||||
snprintf(request.start.session_handle, sizeof(request.start.session_handle), "%s", session_handle);
|
||||
write(self->socket_pair[0], &request, sizeof(request));
|
||||
|
||||
gsr_dbus_response_message response = {0};
|
||||
read(self->socket_pair[0], &response, sizeof(response));
|
||||
|
||||
if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_start: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_start: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code);
|
||||
return response.error.error_code;
|
||||
}
|
||||
|
||||
if(response.type != GSR_DBUS_MESSAGE_RESP_START) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_start: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_START, response.type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(self->screencast_restore_token) {
|
||||
free(self->screencast_restore_token);
|
||||
if(response.start.restore_token[0] == '\0')
|
||||
self->screencast_restore_token = NULL;
|
||||
else
|
||||
self->screencast_restore_token = strdup(response.start.restore_token);
|
||||
}
|
||||
|
||||
*pipewire_node = response.start.pipewire_node;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool gsr_dbus_client_screencast_open_pipewire_remote(gsr_dbus_client *self, const char *session_handle, int *pipewire_fd) {
|
||||
*pipewire_fd = 0;
|
||||
|
||||
gsr_dbus_request_message request = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_REQ_OPEN_PIPEWIRE_REMOTE,
|
||||
.open_pipewire_remote = (gsr_dbus_message_req_open_pipewire_remote) {}
|
||||
};
|
||||
snprintf(request.open_pipewire_remote.session_handle, sizeof(request.open_pipewire_remote.session_handle), "%s", session_handle);
|
||||
write(self->socket_pair[0], &request, sizeof(request));
|
||||
|
||||
gsr_dbus_response_message response = {0};
|
||||
struct iovec iov = {
|
||||
.iov_base = &response,
|
||||
.iov_len = sizeof(response)
|
||||
};
|
||||
|
||||
char msg_control[CMSG_SPACE(sizeof(int))];
|
||||
|
||||
struct msghdr message = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = msg_control,
|
||||
.msg_controllen = sizeof(msg_control)
|
||||
};
|
||||
|
||||
const int bla = recvmsg(self->socket_pair[0], &message, MSG_WAITALL);
|
||||
(void)bla;
|
||||
|
||||
if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(response.type != GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE, response.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message);
|
||||
if(!cmsg || cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: returned message data is missing file descriptor\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(pipewire_fd, CMSG_DATA(cmsg), sizeof(*pipewire_fd));
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* gsr_dbus_client_screencast_get_restore_token(gsr_dbus_client *self) {
|
||||
return self->screencast_restore_token;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef GSR_DBUS_CLIENT_H
|
||||
#define GSR_DBUS_CLIENT_H
|
||||
|
||||
/*
|
||||
Using a client-server architecture is needed for dbus because cap_sys_nice breaks desktop portal.
|
||||
The main binary has cap_sys_nice and we launch a new child-process without it which uses uses desktop portal.
|
||||
*/
|
||||
|
||||
#include "../portal.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
|
||||
typedef struct {
|
||||
int socket_pair[2];
|
||||
char *screencast_restore_token;
|
||||
pid_t pid;
|
||||
} gsr_dbus_client;
|
||||
|
||||
/* Blocking. TODO: Make non-blocking */
|
||||
bool gsr_dbus_client_init(gsr_dbus_client *self, const char *screencast_restore_token);
|
||||
void gsr_dbus_client_deinit(gsr_dbus_client *self);
|
||||
|
||||
/* The follow functions should be called in order to setup ScreenCast properly */
|
||||
/* These functions that return an int return the response status code */
|
||||
int gsr_dbus_client_screencast_create_session(gsr_dbus_client *self, char *session_handle, size_t session_handle_size);
|
||||
/*
|
||||
|capture_type| is a bitmask of gsr_portal_capture_type values. gsr_portal_capture_type values that are not supported by the desktop portal will be ignored.
|
||||
|gsr_portal_cursor_mode| is a bitmask of gsr_portal_cursor_mode values. gsr_portal_cursor_mode values that are not supported will be ignored.
|
||||
*/
|
||||
int gsr_dbus_client_screencast_select_sources(gsr_dbus_client *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode);
|
||||
int gsr_dbus_client_screencast_start(gsr_dbus_client *self, const char *session_handle, uint32_t *pipewire_node);
|
||||
bool gsr_dbus_client_screencast_open_pipewire_remote(gsr_dbus_client *self, const char *session_handle, int *pipewire_fd);
|
||||
const char* gsr_dbus_client_screencast_get_restore_token(gsr_dbus_client *self);
|
||||
|
||||
#endif /* GSR_DBUS_CLIENT_H */
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef GSR_PORTAL_H
|
||||
#define GSR_PORTAL_H
|
||||
|
||||
typedef enum {
|
||||
GSR_PORTAL_CAPTURE_TYPE_MONITOR = 1 << 0,
|
||||
GSR_PORTAL_CAPTURE_TYPE_WINDOW = 1 << 1,
|
||||
GSR_PORTAL_CAPTURE_TYPE_VIRTUAL = 1 << 2,
|
||||
GSR_PORTAL_CAPTURE_TYPE_ALL = GSR_PORTAL_CAPTURE_TYPE_MONITOR | GSR_PORTAL_CAPTURE_TYPE_WINDOW | GSR_PORTAL_CAPTURE_TYPE_VIRTUAL
|
||||
} gsr_portal_capture_type;
|
||||
|
||||
typedef enum {
|
||||
GSR_PORTAL_CURSOR_MODE_HIDDEN = 1 << 0,
|
||||
GSR_PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1,
|
||||
GSR_PORTAL_CURSOR_MODE_METADATA = 1 << 2
|
||||
} gsr_portal_cursor_mode;
|
||||
|
||||
#endif /* GSR_PORTAL_H */
|
||||
@@ -1,86 +0,0 @@
|
||||
#ifndef GSR_DBUS_PROTOCOL_H
|
||||
#define GSR_DBUS_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define GSR_DBUS_PROTOCOL_VERSION 1
|
||||
|
||||
typedef enum {
|
||||
GSR_DBUS_MESSAGE_REQ_CREATE_SESSION,
|
||||
GSR_DBUS_MESSAGE_REQ_SELECT_SOURCES,
|
||||
GSR_DBUS_MESSAGE_REQ_START,
|
||||
GSR_DBUS_MESSAGE_REQ_OPEN_PIPEWIRE_REMOTE
|
||||
} gsr_dbus_message_req_type;
|
||||
|
||||
typedef struct {
|
||||
|
||||
} gsr_dbus_message_req_create_session;
|
||||
|
||||
typedef struct {
|
||||
char session_handle[128];
|
||||
uint32_t capture_type;
|
||||
uint32_t cursor_mode;
|
||||
} gsr_dbus_message_req_select_sources;
|
||||
|
||||
typedef struct {
|
||||
char session_handle[128];
|
||||
} gsr_dbus_message_req_start;
|
||||
|
||||
typedef struct {
|
||||
char session_handle[128];
|
||||
} gsr_dbus_message_req_open_pipewire_remote;
|
||||
|
||||
typedef struct {
|
||||
uint8_t protocol_version;
|
||||
gsr_dbus_message_req_type type;
|
||||
union {
|
||||
gsr_dbus_message_req_create_session create_session;
|
||||
gsr_dbus_message_req_select_sources select_sources;
|
||||
gsr_dbus_message_req_start start;
|
||||
gsr_dbus_message_req_open_pipewire_remote open_pipewire_remote;
|
||||
};
|
||||
} gsr_dbus_request_message;
|
||||
|
||||
typedef enum {
|
||||
GSR_DBUS_MESSAGE_RESP_ERROR,
|
||||
GSR_DBUS_MESSAGE_RESP_CREATE_SESSION,
|
||||
GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES,
|
||||
GSR_DBUS_MESSAGE_RESP_START,
|
||||
GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE
|
||||
} gsr_dbus_message_resp_type;
|
||||
|
||||
typedef struct {
|
||||
uint32_t error_code;
|
||||
char message[128];
|
||||
} gsr_dbus_message_resp_error;
|
||||
|
||||
typedef struct {
|
||||
char session_handle[128];
|
||||
} gsr_dbus_message_resp_create_session;
|
||||
|
||||
typedef struct {
|
||||
|
||||
} gsr_dbus_message_resp_select_sources;
|
||||
|
||||
typedef struct {
|
||||
char restore_token[128];
|
||||
uint32_t pipewire_node;
|
||||
} gsr_dbus_message_resp_start;
|
||||
|
||||
typedef struct {
|
||||
|
||||
} gsr_dbus_message_resp_open_pipewire_remote;
|
||||
|
||||
typedef struct {
|
||||
uint8_t protocol_version;
|
||||
gsr_dbus_message_resp_type type;
|
||||
union {
|
||||
gsr_dbus_message_resp_error error;
|
||||
gsr_dbus_message_resp_create_session create_session;
|
||||
gsr_dbus_message_resp_select_sources select_sources;
|
||||
gsr_dbus_message_resp_start start;
|
||||
gsr_dbus_message_resp_open_pipewire_remote open_pipewire_remote;
|
||||
};
|
||||
} gsr_dbus_response_message;
|
||||
|
||||
#endif /* GSR_DBUS_PROTOCOL_H */
|
||||
@@ -1,175 +0,0 @@
|
||||
#include "../dbus_impl.h"
|
||||
#include "../protocol.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
/* TODO: Error check write/read */
|
||||
|
||||
static int handle_create_session(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_create_session *create_session) {
|
||||
(void)create_session;
|
||||
char *session_handle = NULL;
|
||||
const int status = gsr_dbus_screencast_create_session(dbus, &session_handle);
|
||||
if(status == 0) {
|
||||
gsr_dbus_response_message response = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_RESP_CREATE_SESSION,
|
||||
.create_session = (gsr_dbus_message_resp_create_session) {}
|
||||
};
|
||||
snprintf(response.create_session.session_handle, sizeof(response.create_session.session_handle), "%s", session_handle);
|
||||
free(session_handle);
|
||||
write(rpc_fd, &response, sizeof(response));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int handle_select_sources(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_select_sources *select_sources) {
|
||||
const int status = gsr_dbus_screencast_select_sources(dbus, select_sources->session_handle, select_sources->capture_type, select_sources->cursor_mode);
|
||||
if(status == 0) {
|
||||
gsr_dbus_response_message response = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES,
|
||||
.select_sources = (gsr_dbus_message_resp_select_sources) {}
|
||||
};
|
||||
write(rpc_fd, &response, sizeof(response));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int handle_start(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_start *start) {
|
||||
uint32_t pipewire_node = 0;
|
||||
const int status = gsr_dbus_screencast_start(dbus, start->session_handle, &pipewire_node);
|
||||
if(status == 0) {
|
||||
const char *screencast_restore_token = gsr_dbus_screencast_get_restore_token(dbus);
|
||||
gsr_dbus_response_message response = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_RESP_START,
|
||||
.start = (gsr_dbus_message_resp_start) {
|
||||
.pipewire_node = pipewire_node
|
||||
}
|
||||
};
|
||||
snprintf(response.start.restore_token, sizeof(response.start.restore_token), "%s", screencast_restore_token ? screencast_restore_token : "");
|
||||
write(rpc_fd, &response, sizeof(response));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool handle_open_pipewire_remote(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_open_pipewire_remote *open_pipewire_remote) {
|
||||
int pipewire_fd = 0;
|
||||
const bool success = gsr_dbus_screencast_open_pipewire_remote(dbus, open_pipewire_remote->session_handle, &pipewire_fd);
|
||||
if(success) {
|
||||
gsr_dbus_response_message response = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE,
|
||||
.open_pipewire_remote = (gsr_dbus_message_resp_open_pipewire_remote) {}
|
||||
};
|
||||
|
||||
struct iovec iov = {
|
||||
.iov_base = &response,
|
||||
.iov_len = sizeof(response)
|
||||
};
|
||||
|
||||
char msg_control[CMSG_SPACE(sizeof(int))];
|
||||
|
||||
struct msghdr message = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = msg_control,
|
||||
.msg_controllen = sizeof(msg_control)
|
||||
};
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
int *fds = (int*)CMSG_DATA(cmsg);
|
||||
fds[0] = pipewire_fd;
|
||||
message.msg_controllen = cmsg->cmsg_len;
|
||||
sendmsg(rpc_fd, &message, 0);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc != 3) {
|
||||
fprintf(stderr, "usage: gsr-dbus-server <rpc-fd> <screencast-restore-token>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *rpc_fd_str = argv[1];
|
||||
const char *screencast_restore_token = argv[2];
|
||||
|
||||
int rpc_fd = -1;
|
||||
if(sscanf(rpc_fd_str, "%d", &rpc_fd) != 1) {
|
||||
fprintf(stderr, "gsr-dbus-server error: rpc-fd is not a number: %s\n", rpc_fd_str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(screencast_restore_token[0] == '\0')
|
||||
screencast_restore_token = NULL;
|
||||
|
||||
gsr_dbus dbus;
|
||||
if(!gsr_dbus_init(&dbus, screencast_restore_token))
|
||||
return 1;
|
||||
|
||||
/* Tell client we have started up */
|
||||
write(rpc_fd, "S", 1);
|
||||
|
||||
gsr_dbus_request_message request;
|
||||
for(;;) {
|
||||
read(rpc_fd, &request, sizeof(request));
|
||||
|
||||
if(request.protocol_version != GSR_DBUS_PROTOCOL_VERSION) {
|
||||
gsr_dbus_response_message response = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_RESP_ERROR,
|
||||
.error = (gsr_dbus_message_resp_error) {
|
||||
.error_code = 1
|
||||
}
|
||||
};
|
||||
snprintf(response.error.message, sizeof(response.error.message), "Client uses protocol version %d while the server is using protocol version %d", request.protocol_version, GSR_DBUS_PROTOCOL_VERSION);
|
||||
fprintf(stderr, "gsr-dbus-server error: %s\n", response.error.message);
|
||||
write(rpc_fd, &response, sizeof(response));
|
||||
continue;
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
switch(request.type) {
|
||||
case GSR_DBUS_MESSAGE_REQ_CREATE_SESSION: {
|
||||
status = handle_create_session(&dbus, rpc_fd, &request.create_session);
|
||||
break;
|
||||
}
|
||||
case GSR_DBUS_MESSAGE_REQ_SELECT_SOURCES: {
|
||||
status = handle_select_sources(&dbus, rpc_fd, &request.select_sources);
|
||||
break;
|
||||
}
|
||||
case GSR_DBUS_MESSAGE_REQ_START: {
|
||||
status = handle_start(&dbus, rpc_fd, &request.start);
|
||||
break;
|
||||
}
|
||||
case GSR_DBUS_MESSAGE_REQ_OPEN_PIPEWIRE_REMOTE: {
|
||||
if(!handle_open_pipewire_remote(&dbus, rpc_fd, &request.open_pipewire_remote))
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(status != 0) {
|
||||
gsr_dbus_response_message response = {
|
||||
.protocol_version = GSR_DBUS_PROTOCOL_VERSION,
|
||||
.type = GSR_DBUS_MESSAGE_RESP_ERROR,
|
||||
.error = (gsr_dbus_message_resp_error) {
|
||||
.error_code = status
|
||||
}
|
||||
};
|
||||
snprintf(response.error.message, sizeof(response.error.message), "%s", "Failed to handle request");
|
||||
write(rpc_fd, &response, sizeof(response));
|
||||
}
|
||||
}
|
||||
|
||||
gsr_dbus_deinit(&dbus);
|
||||
return 0;
|
||||
}
|
||||
@@ -22,12 +22,13 @@ typedef struct {
|
||||
} gsr_capture_metadata;
|
||||
|
||||
struct gsr_capture {
|
||||
/* These methods should not be called manually. Call gsr_capture_* instead */
|
||||
/* These methods should not be called manually. Call gsr_capture_* instead. |capture_metdata->width| and |capture_metadata->height| should be set by this function */
|
||||
int (*start)(gsr_capture *cap, gsr_capture_metadata *capture_metadata);
|
||||
void (*on_event)(gsr_capture *cap, gsr_egl *egl); /* can be NULL */
|
||||
void (*tick)(gsr_capture *cap); /* can be NULL. If there is an event then |on_event| is called before this */
|
||||
bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL. If NULL, return false */
|
||||
int (*capture)(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion);
|
||||
bool (*capture_has_synchronous_task)(gsr_capture *cap); /* can be NULL. If this returns true then the time spent in |capture| is ignored for video/audio (capture is paused while the synchronous task happens) */
|
||||
int (*capture)(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion); /* Return 0 if the frame was captured */
|
||||
bool (*uses_external_image)(gsr_capture *cap); /* can be NULL. If NULL, return false */
|
||||
bool (*set_hdr_metadata)(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata); /* can be NULL. If NULL, return false */
|
||||
uint64_t (*get_window_id)(gsr_capture *cap); /* can be NULL. Returns 0 if unknown */
|
||||
|
||||
@@ -50,6 +50,7 @@ typedef struct {
|
||||
|
||||
gsr_color_range color_range;
|
||||
bool load_external_image_shader;
|
||||
bool force_graphics_shader;
|
||||
} gsr_color_conversion_params;
|
||||
|
||||
typedef struct {
|
||||
@@ -77,6 +78,7 @@ void gsr_color_conversion_deinit(gsr_color_conversion *self);
|
||||
|
||||
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture, bool alpha_blending);
|
||||
void gsr_color_conversion_clear(gsr_color_conversion *self);
|
||||
void gsr_color_conversion_read_destination_texture(gsr_color_conversion *self, int destination_texture_index, int x, int y, int width, int height, unsigned int color_format, unsigned int data_format, void *pixels);
|
||||
|
||||
gsr_rotation gsr_monitor_rotation_to_rotation(gsr_monitor_rotation monitor_rotation);
|
||||
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
#ifndef GSR_DBUS_H
|
||||
#define GSR_DBUS_H
|
||||
|
||||
#include "portal.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
#define DBUS_RANDOM_STR_SIZE 16
|
||||
|
||||
typedef enum {
|
||||
GSR_PORTAL_CAPTURE_TYPE_MONITOR = 1 << 0,
|
||||
GSR_PORTAL_CAPTURE_TYPE_WINDOW = 1 << 1,
|
||||
GSR_PORTAL_CAPTURE_TYPE_VIRTUAL = 1 << 2,
|
||||
GSR_PORTAL_CAPTURE_TYPE_ALL = GSR_PORTAL_CAPTURE_TYPE_MONITOR | GSR_PORTAL_CAPTURE_TYPE_WINDOW | GSR_PORTAL_CAPTURE_TYPE_VIRTUAL
|
||||
} gsr_portal_capture_type;
|
||||
|
||||
typedef enum {
|
||||
GSR_PORTAL_CURSOR_MODE_HIDDEN = 1 << 0,
|
||||
GSR_PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1,
|
||||
GSR_PORTAL_CURSOR_MODE_METADATA = 1 << 2
|
||||
} gsr_portal_cursor_mode;
|
||||
|
||||
typedef struct {
|
||||
DBusConnection *con;
|
||||
DBusError err;
|
||||
@@ -48,6 +48,8 @@ typedef void(*__GLXextFuncPtr)(void);
|
||||
#define EGL_OPENGL_ES_API 0x30A0
|
||||
#define EGL_OPENGL_BIT 0x0008
|
||||
#define EGL_OPENGL_ES_BIT 0x0001
|
||||
#define EGL_OPENGL_ES2_BIT 0x0004
|
||||
#define EGL_OPENGL_ES3_BIT 0x00000040
|
||||
#define EGL_NONE 0x3038
|
||||
#define EGL_CONTEXT_CLIENT_VERSION 0x3098
|
||||
#define EGL_BACK_BUFFER 0x3084
|
||||
@@ -141,8 +143,6 @@ typedef void(*__GLXextFuncPtr)(void);
|
||||
#define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS 0x90EB
|
||||
#define GL_TEXTURE0 0x84C0
|
||||
#define GL_TEXTURE1 0x84C1
|
||||
#define GL_CLAMP_TO_BORDER 0x812D
|
||||
#define GL_TEXTURE_BORDER_COLOR 0x1004
|
||||
#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020
|
||||
#define GL_ALL_BARRIER_BITS 0xFFFFFFFF
|
||||
|
||||
@@ -265,6 +265,7 @@ struct gsr_egl {
|
||||
void (*glGetTexLevelParameteriv)(unsigned int target, int level, unsigned int pname, int *params);
|
||||
void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels);
|
||||
void (*glTexSubImage2D)(unsigned int target, int level, int xoffset, int yoffset, int width, int height, unsigned format, unsigned type, const void *pixels);
|
||||
void (*glTexStorage2D)(unsigned int target, int levels, unsigned int internalformat, int width, int height);
|
||||
void (*glGetTexImage)(unsigned int target, int level, unsigned int format, unsigned int type, void *pixels);
|
||||
void (*glGenFramebuffers)(int n, unsigned int *framebuffers);
|
||||
void (*glBindFramebuffer)(unsigned int target, unsigned int framebuffer);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <spa/param/video/format.h>
|
||||
|
||||
#define GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS 1024
|
||||
#define GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS 12
|
||||
#define GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS 6
|
||||
#define GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES 4
|
||||
|
||||
typedef struct gsr_egl gsr_egl;
|
||||
@@ -65,6 +65,7 @@ typedef struct {
|
||||
struct spa_video_info format;
|
||||
int server_version_sync;
|
||||
bool negotiated;
|
||||
bool renegotiated;
|
||||
bool damaged;
|
||||
|
||||
struct {
|
||||
@@ -94,6 +95,9 @@ typedef struct {
|
||||
|
||||
uint64_t modifiers[GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS];
|
||||
size_t num_modifiers;
|
||||
|
||||
bool paused;
|
||||
double paused_start_secs;
|
||||
} gsr_pipewire_video;
|
||||
|
||||
/*
|
||||
@@ -108,5 +112,6 @@ void gsr_pipewire_video_deinit(gsr_pipewire_video *self);
|
||||
bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map texture_map, gsr_pipewire_video_region *region, gsr_pipewire_video_region *cursor_region, gsr_pipewire_video_dmabuf_data *dmabuf_data, int *num_dmabuf_data, uint32_t *fourcc, uint64_t *modifiers, bool *using_external_image);
|
||||
bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self);
|
||||
void gsr_pipewire_video_clear_damage(gsr_pipewire_video *self);
|
||||
bool gsr_pipewire_video_should_restart(gsr_pipewire_video *self);
|
||||
|
||||
#endif /* GSR_PIPEWIRE_VIDEO_H */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.5.7', default_options : ['warning_level=2'])
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.6.0', default_options : ['warning_level=2'])
|
||||
|
||||
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
|
||||
if get_option('buildtype') == 'debug'
|
||||
@@ -76,7 +76,7 @@ uses_pipewire = false
|
||||
if get_option('portal') == true
|
||||
src += [
|
||||
'src/capture/portal.c',
|
||||
'dbus/client/dbus_client.c',
|
||||
'src/dbus.c',
|
||||
'src/pipewire_video.c',
|
||||
]
|
||||
add_project_arguments('-DGSR_PORTAL', language : ['c', 'cpp'])
|
||||
@@ -95,6 +95,7 @@ if uses_pipewire == true
|
||||
dep += [
|
||||
dependency('libpipewire-0.3'),
|
||||
dependency('libspa-0.2'),
|
||||
dependency('dbus-1'),
|
||||
]
|
||||
endif
|
||||
|
||||
@@ -103,10 +104,6 @@ add_project_arguments('-DGSR_VERSION="' + meson.project_version() + '"', languag
|
||||
executable('gsr-kms-server', 'kms/server/kms_server.c', dependencies : dependency('libdrm'), c_args : '-fstack-protector-all', install : true)
|
||||
executable('gpu-screen-recorder', src, dependencies : dep, install : true)
|
||||
|
||||
if get_option('portal') == true
|
||||
executable('gsr-dbus-server', ['dbus/server/dbus_server.c', 'dbus/dbus_impl.c'], dependencies : dependency('dbus-1'), install : true)
|
||||
endif
|
||||
|
||||
if get_option('systemd') == true
|
||||
install_data(files('extra/gpu-screen-recorder.service'), install_dir : 'lib/systemd/user')
|
||||
endif
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "gpu-screen-recorder"
|
||||
type = "executable"
|
||||
version = "5.5.7"
|
||||
version = "5.6.0"
|
||||
platforms = ["posix"]
|
||||
|
||||
[config]
|
||||
ignore_dirs = ["kms/server", "build", "debug-build", "dbus/server"]
|
||||
ignore_dirs = ["kms/server", "build", "debug-build"]
|
||||
#error_on_warning = "true"
|
||||
|
||||
[define]
|
||||
|
||||
6
scripts/toggle-recording.sh
Executable file
6
scripts/toggle-recording.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
killall -SIGINT gpu-screen-recorder && sleep 0.5 && notify-send -t 1500 -u low 'GPU Screen Recorder' 'Stopped recording' && exit 0;
|
||||
video="$HOME/Videos/$(date +"Video_%Y-%m-%d_%H-%M-%S.mp4")"
|
||||
notify-send -t 1500 -u low 'GPU Screen Recorder' "Started recording video to $video"
|
||||
gpu-screen-recorder -w screen -f 60 -a "default_output" -o "$video"
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <xf86drm.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <drm_fourcc.h>
|
||||
|
||||
#include <libavutil/mastering_display_metadata.h>
|
||||
|
||||
@@ -108,22 +108,14 @@ static int max_int(int a, int b) {
|
||||
}
|
||||
|
||||
static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) {
|
||||
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
||||
self->params.egl->glGenTextures(1, &self->input_texture_id);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->input_texture_id);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
self->params.egl->glGenTextures(1, &self->external_input_texture_id);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->external_input_texture_id);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
|
||||
@@ -133,9 +125,6 @@ static void gsr_capture_kms_create_input_texture_ids(gsr_capture_kms *self) {
|
||||
|
||||
self->params.egl->glGenTextures(1, &self->cursor_texture_id);
|
||||
self->params.egl->glBindTexture(cursor_texture_id_target, self->cursor_texture_id);
|
||||
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(cursor_texture_id_target, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->params.egl->glTexParameteri(cursor_texture_id_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->params.egl->glBindTexture(cursor_texture_id_target, 0);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "../../include/color_conversion.h"
|
||||
#include "../../include/egl.h"
|
||||
#include "../../include/utils.h"
|
||||
#include "../../dbus/client/dbus_client.h"
|
||||
#include "../../include/dbus.h"
|
||||
#include "../../include/pipewire_video.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -11,18 +11,36 @@
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE 60
|
||||
|
||||
typedef enum {
|
||||
PORTAL_CAPTURE_SETUP_IDLE,
|
||||
PORTAL_CAPTURE_SETUP_IN_PROGRESS,
|
||||
PORTAL_CAPTURE_SETUP_FINISHED,
|
||||
PORTAL_CAPTURE_SETUP_FAILED
|
||||
} gsr_portal_capture_setup_state;
|
||||
|
||||
typedef struct {
|
||||
gsr_capture_portal_params params;
|
||||
|
||||
gsr_texture_map texture_map;
|
||||
|
||||
gsr_dbus_client dbus_client;
|
||||
char session_handle[128];
|
||||
gsr_dbus dbus;
|
||||
char *session_handle;
|
||||
|
||||
gsr_pipewire_video pipewire;
|
||||
vec2i capture_size;
|
||||
gsr_pipewire_video_dmabuf_data dmabuf_data[GSR_PIPEWIRE_VIDEO_DMABUF_MAX_PLANES];
|
||||
int num_dmabuf_data;
|
||||
|
||||
gsr_pipewire_video_region region;
|
||||
gsr_pipewire_video_region cursor_region;
|
||||
uint32_t pipewire_fourcc;
|
||||
uint64_t pipewire_modifiers;
|
||||
bool using_external_image;
|
||||
|
||||
bool should_stop;
|
||||
bool stop_is_error;
|
||||
} gsr_capture_portal;
|
||||
|
||||
static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) {
|
||||
@@ -53,35 +71,24 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) {
|
||||
|
||||
gsr_capture_portal_cleanup_plane_fds(self);
|
||||
gsr_pipewire_video_deinit(&self->pipewire);
|
||||
gsr_dbus_client_deinit(&self->dbus_client);
|
||||
gsr_dbus_deinit(&self->dbus);
|
||||
}
|
||||
|
||||
static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) {
|
||||
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
||||
self->params.egl->glGenTextures(1, &self->texture_map.texture_id);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.texture_id);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
self->params.egl->glGenTextures(1, &self->texture_map.external_texture_id);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->texture_map.external_texture_id);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
|
||||
|
||||
self->params.egl->glGenTextures(1, &self->texture_map.cursor_texture_id);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->texture_map.cursor_texture_id);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
@@ -188,36 +195,36 @@ static int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire
|
||||
if(self->params.restore_portal_session)
|
||||
gsr_capture_portal_get_restore_token_from_cache(restore_token, sizeof(restore_token), self->params.portal_session_token_filepath);
|
||||
|
||||
if(!gsr_dbus_client_init(&self->dbus_client, restore_token))
|
||||
if(!gsr_dbus_init(&self->dbus, restore_token))
|
||||
return -1;
|
||||
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: CreateSession\n");
|
||||
response_status = gsr_dbus_client_screencast_create_session(&self->dbus_client, self->session_handle, sizeof(self->session_handle));
|
||||
response_status = gsr_dbus_screencast_create_session(&self->dbus, &self->session_handle);
|
||||
if(response_status != 0) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: CreateSession failed\n");
|
||||
return response_status;
|
||||
}
|
||||
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: SelectSources\n");
|
||||
response_status = gsr_dbus_client_screencast_select_sources(&self->dbus_client, self->session_handle, GSR_PORTAL_CAPTURE_TYPE_ALL, self->params.record_cursor ? GSR_PORTAL_CURSOR_MODE_EMBEDDED : GSR_PORTAL_CURSOR_MODE_HIDDEN);
|
||||
response_status = gsr_dbus_screencast_select_sources(&self->dbus, self->session_handle, GSR_PORTAL_CAPTURE_TYPE_ALL, self->params.record_cursor ? GSR_PORTAL_CURSOR_MODE_EMBEDDED : GSR_PORTAL_CURSOR_MODE_HIDDEN);
|
||||
if(response_status != 0) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: SelectSources failed\n");
|
||||
return response_status;
|
||||
}
|
||||
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: Start\n");
|
||||
response_status = gsr_dbus_client_screencast_start(&self->dbus_client, self->session_handle, pipewire_node);
|
||||
response_status = gsr_dbus_screencast_start(&self->dbus, self->session_handle, pipewire_node);
|
||||
if(response_status != 0) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: Start failed\n");
|
||||
return response_status;
|
||||
}
|
||||
|
||||
const char *screencast_restore_token = gsr_dbus_client_screencast_get_restore_token(&self->dbus_client);
|
||||
const char *screencast_restore_token = gsr_dbus_screencast_get_restore_token(&self->dbus);
|
||||
if(screencast_restore_token)
|
||||
gsr_capture_portal_save_restore_token(screencast_restore_token, self->params.portal_session_token_filepath);
|
||||
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: OpenPipeWireRemote\n");
|
||||
if(!gsr_dbus_client_screencast_open_pipewire_remote(&self->dbus_client, self->session_handle, pipewire_fd)) {
|
||||
if(!gsr_dbus_screencast_open_pipewire_remote(&self->dbus, self->session_handle, pipewire_fd)) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: OpenPipeWireRemote failed\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -227,19 +234,13 @@ static int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire
|
||||
}
|
||||
|
||||
static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
|
||||
gsr_pipewire_video_region region = {0, 0, 0, 0};
|
||||
gsr_pipewire_video_region cursor_region = {0, 0, 0, 0};
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_start: waiting for pipewire negotiation\n");
|
||||
|
||||
const double start_time = clock_get_monotonic_seconds();
|
||||
while(clock_get_monotonic_seconds() - start_time < 5.0) {
|
||||
bool uses_external_image = false;
|
||||
uint32_t fourcc = 0;
|
||||
uint64_t modifiers = 0;
|
||||
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &fourcc, &modifiers, &uses_external_image)) {
|
||||
gsr_capture_portal_cleanup_plane_fds(self);
|
||||
self->capture_size.x = region.width;
|
||||
self->capture_size.y = region.height;
|
||||
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
|
||||
self->capture_size.x = self->region.width;
|
||||
self->capture_size.y = self->region.height;
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire negotiation finished\n");
|
||||
return true;
|
||||
}
|
||||
@@ -250,45 +251,51 @@ static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
|
||||
gsr_capture_portal *self = cap->priv;
|
||||
|
||||
static int gsr_capture_portal_setup(gsr_capture_portal *self, int fps) {
|
||||
gsr_capture_portal_create_input_textures(self);
|
||||
|
||||
int pipewire_fd = 0;
|
||||
uint32_t pipewire_node = 0;
|
||||
const int response_status = gsr_capture_portal_setup_dbus(self, &pipewire_fd, &pipewire_node);
|
||||
if(response_status != 0) {
|
||||
gsr_capture_portal_stop(self);
|
||||
// Response status values:
|
||||
// 0: Success, the request is carried out
|
||||
// 1: The user cancelled the interaction
|
||||
// 2: The user interaction was ended in some other way
|
||||
// Response status value 2 happens usually if there was some kind of error in the desktop portal on the system
|
||||
if(response_status == 2) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n");
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n");
|
||||
return 50;
|
||||
} else if(response_status == 1) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n");
|
||||
return 60;
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n");
|
||||
return PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_start: setting up pipewire\n");
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_setup: setting up pipewire\n");
|
||||
/* TODO: support hdr when pipewire supports it */
|
||||
/* gsr_pipewire closes the pipewire fd, even on failure */
|
||||
if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, capture_metadata->fps, self->params.record_cursor, self->params.egl)) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_start: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node);
|
||||
gsr_capture_portal_stop(self);
|
||||
if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, fps, self->params.record_cursor, self->params.egl)) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_portal_setup: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node);
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire setup finished\n");
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_setup: pipewire setup finished\n");
|
||||
|
||||
if(!gsr_capture_portal_get_frame_dimensions(self)) {
|
||||
gsr_capture_portal_stop(self);
|
||||
if(!gsr_capture_portal_get_frame_dimensions(self))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
|
||||
gsr_capture_portal *self = cap->priv;
|
||||
|
||||
const int result = gsr_capture_portal_setup(self, capture_metadata->fps);
|
||||
if(result != 0) {
|
||||
gsr_capture_portal_stop(self);
|
||||
return result;
|
||||
}
|
||||
|
||||
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
|
||||
@@ -307,24 +314,40 @@ static int max_int(int a, int b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
static bool gsr_capture_portal_capture_has_synchronous_task(gsr_capture *cap) {
|
||||
gsr_capture_portal *self = cap->priv;
|
||||
return gsr_pipewire_video_should_restart(&self->pipewire);
|
||||
}
|
||||
|
||||
static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
|
||||
(void)color_conversion;
|
||||
gsr_capture_portal *self = cap->priv;
|
||||
|
||||
/* TODO: Handle formats other than RGB(a) */
|
||||
gsr_pipewire_video_region region = {0, 0, 0, 0};
|
||||
gsr_pipewire_video_region cursor_region = {0, 0, 0, 0};
|
||||
uint32_t pipewire_fourcc = 0;
|
||||
uint64_t pipewire_modifiers = 0;
|
||||
bool using_external_image = false;
|
||||
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, ®ion, &cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &pipewire_fourcc, &pipewire_modifiers, &using_external_image)) {
|
||||
if(region.width != self->capture_size.x || region.height != self->capture_size.y) {
|
||||
self->capture_size.x = region.width;
|
||||
self->capture_size.y = region.height;
|
||||
gsr_color_conversion_clear(color_conversion);
|
||||
if(self->should_stop)
|
||||
return -1;
|
||||
|
||||
if(gsr_pipewire_video_should_restart(&self->pipewire)) {
|
||||
fprintf(stderr, "gsr info: gsr_capture_portal_capture: pipewire capture was paused, trying to start capture again\n");
|
||||
gsr_capture_portal_stop(self);
|
||||
const int result = gsr_capture_portal_setup(self, capture_metadata->fps);
|
||||
if(result != 0) {
|
||||
self->stop_is_error = result != PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE;
|
||||
self->should_stop = true;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* TODO: Handle formats other than RGB(A) */
|
||||
if(self->num_dmabuf_data == 0) {
|
||||
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
|
||||
if(self->region.width != self->capture_size.x || self->region.height != self->capture_size.y) {
|
||||
self->capture_size.x = self->region.width;
|
||||
self->capture_size.y = self->region.height;
|
||||
gsr_color_conversion_clear(color_conversion);
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
|
||||
@@ -338,27 +361,27 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
|
||||
|
||||
// TODO: Handle region crop
|
||||
|
||||
gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
|
||||
gsr_color_conversion_draw(color_conversion, self->using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
|
||||
target_pos, output_size,
|
||||
(vec2i){region.x, region.y}, self->capture_size, self->capture_size,
|
||||
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, using_external_image, false);
|
||||
(vec2i){self->region.x, self->region.y}, self->capture_size, self->capture_size,
|
||||
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, self->using_external_image, false);
|
||||
|
||||
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && cursor_region.width > 0) {
|
||||
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->cursor_region.width > 0) {
|
||||
const vec2d scale = {
|
||||
self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
|
||||
self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
|
||||
};
|
||||
|
||||
const vec2i cursor_pos = {
|
||||
target_pos.x + (cursor_region.x * scale.x),
|
||||
target_pos.y + (cursor_region.y * scale.y)
|
||||
target_pos.x + (self->cursor_region.x * scale.x),
|
||||
target_pos.y + (self->cursor_region.y * scale.y)
|
||||
};
|
||||
|
||||
self->params.egl->glEnable(GL_SCISSOR_TEST);
|
||||
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
|
||||
gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id,
|
||||
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width * scale.x, cursor_region.height * scale.y},
|
||||
(vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height}, (vec2i){cursor_region.width, cursor_region.height},
|
||||
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){self->cursor_region.width * scale.x, self->cursor_region.height * scale.y},
|
||||
(vec2i){0, 0}, (vec2i){self->cursor_region.width, self->cursor_region.height}, (vec2i){self->cursor_region.width, self->cursor_region.height},
|
||||
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
|
||||
self->params.egl->glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
@@ -376,6 +399,13 @@ static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gsr_capture_portal_should_stop(gsr_capture *cap, bool *err) {
|
||||
gsr_capture_portal *self = cap->priv;
|
||||
if(err)
|
||||
*err = self->stop_is_error;
|
||||
return self->should_stop;
|
||||
}
|
||||
|
||||
static bool gsr_capture_portal_is_damaged(gsr_capture *cap) {
|
||||
gsr_capture_portal *self = cap->priv;
|
||||
return gsr_pipewire_video_is_damaged(&self->pipewire);
|
||||
@@ -417,7 +447,8 @@ gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params)
|
||||
*cap = (gsr_capture) {
|
||||
.start = gsr_capture_portal_start,
|
||||
.tick = NULL,
|
||||
.should_stop = NULL,
|
||||
.should_stop = gsr_capture_portal_should_stop,
|
||||
.capture_has_synchronous_task = gsr_capture_portal_capture_has_synchronous_task,
|
||||
.capture = gsr_capture_portal_capture,
|
||||
.uses_external_image = gsr_capture_portal_uses_external_image,
|
||||
.is_damaged = gsr_capture_portal_is_damaged,
|
||||
|
||||
@@ -89,15 +89,13 @@ static void get_compute_shader_header(char *header, size_t header_size, bool ext
|
||||
if(external_texture) {
|
||||
snprintf(header, header_size,
|
||||
"#version 310 es\n"
|
||||
"#extension GL_ARB_compute_shader: enable\n"
|
||||
"#extension GL_OES_EGL_image_external : enable\n"
|
||||
"#extension GL_OES_EGL_image_external_essl3 : require\n"
|
||||
"layout(binding = 0) uniform highp samplerExternalOES img_input;\n"
|
||||
"layout(binding = 1) uniform highp sampler2D img_background;\n");
|
||||
} else {
|
||||
snprintf(header, header_size,
|
||||
"#version 420\n"
|
||||
"#extension GL_ARB_compute_shader: enable\n"
|
||||
"#version 310 es\n"
|
||||
"layout(binding = 0) uniform highp sampler2D img_input;\n"
|
||||
"layout(binding = 1) uniform highp sampler2D img_background;\n");
|
||||
}
|
||||
@@ -109,7 +107,7 @@ static int load_compute_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_com
|
||||
char header[512];
|
||||
get_compute_shader_header(header, sizeof(header), external_texture);
|
||||
|
||||
char compute_shader[2048];
|
||||
char compute_shader[4096];
|
||||
snprintf(compute_shader, sizeof(compute_shader),
|
||||
"%s"
|
||||
"layout (local_size_x = %d, local_size_y = %d, local_size_z = 1) in;\n"
|
||||
@@ -127,12 +125,16 @@ static int load_compute_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_com
|
||||
" ivec2 output_size = textureSize(img_background, 0);\n"
|
||||
" vec2 rotated_texel_coord = vec2(texel_coord - source_position - size_shift) * rotation_matrix + vec2(size_shift) + 0.5;\n"
|
||||
" vec2 output_texel_coord = vec2(texel_coord - source_position + target_position) + 0.5;\n"
|
||||
" vec4 source_color = texture(img_input, rotated_texel_coord/vec2(size));\n"
|
||||
" vec2 source_color_coords = rotated_texel_coord/vec2(size);\n"
|
||||
" vec4 source_color = texture(img_input, source_color_coords);\n"
|
||||
" if(source_color_coords.x > 1.0 || source_color_coords.y > 1.0)\n"
|
||||
" source_color.rgba = vec4(0.0, 0.0, 0.0, %s);\n"
|
||||
" vec4 source_color_yuv = RGBtoYUV * vec4(source_color.rgb, 1.0);\n"
|
||||
" vec4 output_color_yuv = %s;\n"
|
||||
" float y_color = mix(output_color_yuv.r, source_color_yuv.r, source_color.a);\n"
|
||||
" imageStore(img_output, texel_coord + target_position, vec4(y_color, 1.0, 1.0, 1.0));\n"
|
||||
"}\n", header, max_local_size_dim, max_local_size_dim, color_transform_matrix,
|
||||
alpha_blending ? "0.0" : "1.0",
|
||||
alpha_blending ? "texture(img_background, output_texel_coord/vec2(output_size))" : "source_color_yuv");
|
||||
|
||||
if(gsr_shader_init(shader, egl, NULL, NULL, compute_shader) != 0)
|
||||
@@ -151,7 +153,7 @@ static int load_compute_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_co
|
||||
char header[512];
|
||||
get_compute_shader_header(header, sizeof(header), external_texture);
|
||||
|
||||
char compute_shader[2048];
|
||||
char compute_shader[4096];
|
||||
snprintf(compute_shader, sizeof(compute_shader),
|
||||
"%s"
|
||||
"layout (local_size_x = %d, local_size_y = %d, local_size_z = 1) in;\n"
|
||||
@@ -169,12 +171,16 @@ static int load_compute_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_co
|
||||
" ivec2 output_size = textureSize(img_background, 0);\n"
|
||||
" vec2 rotated_texel_coord = vec2(texel_coord - source_position - size_shift) * rotation_matrix + vec2(size_shift) + 0.5;\n"
|
||||
" vec2 output_texel_coord = vec2(texel_coord - source_position + target_position) + 0.5;\n"
|
||||
" vec4 source_color = texture(img_input, rotated_texel_coord/vec2(size>>1));\n" // size/2
|
||||
" vec2 source_color_coords = rotated_texel_coord/vec2(size>>1);\n"
|
||||
" vec4 source_color = texture(img_input, source_color_coords);\n" // size/2
|
||||
" if(source_color_coords.x > 1.0 || source_color_coords.y > 1.0)\n"
|
||||
" source_color.rgba = vec4(0.0, 0.0, 0.0, %s);\n"
|
||||
" vec4 source_color_yuv = RGBtoYUV * vec4(source_color.rgb, 1.0);\n"
|
||||
" vec4 output_color_yuv = %s;\n"
|
||||
" vec2 uv_color = mix(output_color_yuv.rg, source_color_yuv.gb, source_color.a);\n"
|
||||
" imageStore(img_output, texel_coord + target_position, vec4(uv_color, 1.0, 1.0));\n"
|
||||
"}\n", header, max_local_size_dim, max_local_size_dim, color_transform_matrix,
|
||||
alpha_blending ? "0.0" : "1.0",
|
||||
alpha_blending ? "texture(img_background, output_texel_coord/vec2(output_size))" : "source_color_yuv");
|
||||
|
||||
if(gsr_shader_init(shader, egl, NULL, NULL, compute_shader) != 0)
|
||||
@@ -191,10 +197,11 @@ static int load_compute_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_c
|
||||
char header[512];
|
||||
get_compute_shader_header(header, sizeof(header), external_texture);
|
||||
|
||||
char compute_shader[2048];
|
||||
char compute_shader[4096];
|
||||
snprintf(compute_shader, sizeof(compute_shader),
|
||||
"%s"
|
||||
"layout (local_size_x = %d, local_size_y = %d, local_size_z = 1) in;\n"
|
||||
"precision highp float;\n"
|
||||
"uniform ivec2 source_position;\n"
|
||||
"uniform ivec2 target_position;\n"
|
||||
"uniform vec2 scale;\n"
|
||||
@@ -207,11 +214,15 @@ static int load_compute_shader_rgb(gsr_shader *shader, gsr_egl *egl, gsr_color_c
|
||||
" ivec2 output_size = textureSize(img_background, 0);\n"
|
||||
" vec2 rotated_texel_coord = vec2(texel_coord - source_position - size_shift) * rotation_matrix + vec2(size_shift) + 0.5;\n"
|
||||
" vec2 output_texel_coord = vec2(texel_coord - source_position + target_position) + 0.5;\n"
|
||||
" vec4 source_color = texture(img_input, rotated_texel_coord/vec2(size));\n"
|
||||
" vec2 source_color_coords = rotated_texel_coord/vec2(size);\n"
|
||||
" vec4 source_color = texture(img_input, source_color_coords);\n"
|
||||
" if(source_color_coords.x > 1.0 || source_color_coords.y > 1.0)\n"
|
||||
" source_color.rgba = vec4(0.0, 0.0, 0.0, %s);\n"
|
||||
" vec4 output_color = %s;\n"
|
||||
" vec3 color = mix(output_color.rgb, source_color.rgb, source_color.a);\n"
|
||||
" imageStore(img_output, texel_coord + target_position, vec4(color, 1.0));\n"
|
||||
"}\n", header, max_local_size_dim, max_local_size_dim,
|
||||
alpha_blending ? "0.0" : "1.0",
|
||||
alpha_blending ? "texture(img_background, output_texel_coord/vec2(output_size))" : "source_color");
|
||||
|
||||
if(gsr_shader_init(shader, egl, NULL, NULL, compute_shader) != 0)
|
||||
@@ -620,20 +631,33 @@ int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conver
|
||||
}
|
||||
}
|
||||
|
||||
if(!gsr_color_conversion_load_compute_shaders(self)) {
|
||||
if(self->params.force_graphics_shader) {
|
||||
self->compute_shaders_failed_to_load = true;
|
||||
fprintf(stderr, "gsr info: failed to load one or more compute shaders, run gpu-screen-recorder with the '-gl-debug yes' option to see why. Falling back to slower graphics shader instead\n");
|
||||
self->external_compute_shaders_failed_to_load = true;
|
||||
|
||||
if(!gsr_color_conversion_load_graphics_shaders(self))
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(self->params.load_external_image_shader) {
|
||||
if(!gsr_color_conversion_load_external_compute_shaders(self)) {
|
||||
self->external_compute_shaders_failed_to_load = true;
|
||||
fprintf(stderr, "gsr info: failed to load one or more external compute shaders, run gpu-screen-recorder with the '-gl-debug yes' option to see why. Falling back to slower graphics shader instead\n");
|
||||
if(self->params.load_external_image_shader) {
|
||||
if(!gsr_color_conversion_load_external_graphics_shaders(self))
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
if(!gsr_color_conversion_load_compute_shaders(self)) {
|
||||
self->compute_shaders_failed_to_load = true;
|
||||
fprintf(stderr, "gsr info: failed to load one or more compute shaders, run gpu-screen-recorder with the '-gl-debug yes' option to see why. Falling back to slower graphics shader instead\n");
|
||||
if(!gsr_color_conversion_load_graphics_shaders(self))
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(self->params.load_external_image_shader) {
|
||||
if(!gsr_color_conversion_load_external_compute_shaders(self)) {
|
||||
self->external_compute_shaders_failed_to_load = true;
|
||||
fprintf(stderr, "gsr info: failed to load one or more external compute shaders, run gpu-screen-recorder with the '-gl-debug yes' option to see why. Falling back to slower graphics shader instead\n");
|
||||
if(!gsr_color_conversion_load_external_graphics_shaders(self))
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(load_framebuffers(self) != 0)
|
||||
@@ -998,6 +1022,13 @@ void gsr_color_conversion_clear(gsr_color_conversion *self) {
|
||||
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void gsr_color_conversion_read_destination_texture(gsr_color_conversion *self, int destination_texture_index, int x, int y, int width, int height, unsigned int color_format, unsigned int data_format, void *pixels) {
|
||||
assert(destination_texture_index >= 0 && destination_texture_index < self->params.num_destination_textures);
|
||||
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[destination_texture_index]);
|
||||
self->params.egl->glReadPixels(x, y, width, height, color_format, data_format, pixels);
|
||||
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
gsr_rotation gsr_monitor_rotation_to_rotation(gsr_monitor_rotation monitor_rotation) {
|
||||
return (gsr_rotation)monitor_rotation;
|
||||
}
|
||||
|
||||
@@ -56,10 +56,6 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI
|
||||
self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, cursor_data);
|
||||
free(cursor_data);
|
||||
|
||||
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "dbus_impl.h"
|
||||
#include "../include/dbus.h"
|
||||
|
||||
#include <sys/random.h>
|
||||
|
||||
11
src/egl.c
11
src/egl.c
@@ -33,10 +33,9 @@ static bool gsr_egl_create_window(gsr_egl *self) {
|
||||
EGLConfig ecfg;
|
||||
int32_t num_config = 0;
|
||||
|
||||
// TODO: Use EGL_OPENGL_ES_BIT as amd requires that for external texture, but that breaks software encoding
|
||||
const int32_t attr[] = {
|
||||
EGL_BUFFER_SIZE, 24,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
|
||||
EGL_NONE, EGL_NONE
|
||||
};
|
||||
|
||||
@@ -45,8 +44,7 @@ static bool gsr_egl_create_window(gsr_egl *self) {
|
||||
EGL_NONE, EGL_NONE
|
||||
};
|
||||
|
||||
// TODO: Use EGL_OPENGL_ES_API as amd requires that for external texture, but that breaks software encoding
|
||||
self->eglBindAPI(EGL_OPENGL_API);
|
||||
self->eglBindAPI(EGL_OPENGL_ES_API);
|
||||
|
||||
self->egl_display = self->eglGetDisplay((EGLNativeDisplayType)gsr_window_get_display(self->window));
|
||||
if(!self->egl_display) {
|
||||
@@ -279,6 +277,7 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
|
||||
{ (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" },
|
||||
{ (void**)&self->glTexImage2D, "glTexImage2D" },
|
||||
{ (void**)&self->glTexSubImage2D, "glTexSubImage2D" },
|
||||
{ (void**)&self->glTexStorage2D, "glTexStorage2D" },
|
||||
{ (void**)&self->glGetTexImage, "glGetTexImage" },
|
||||
{ (void**)&self->glGenFramebuffers, "glGenFramebuffers" },
|
||||
{ (void**)&self->glBindFramebuffer, "glBindFramebuffer" },
|
||||
@@ -460,9 +459,9 @@ bool gsr_egl_load(gsr_egl *self, gsr_window *window, bool is_monitor_capture, bo
|
||||
/* This fixes nvenc codecs unable to load on openSUSE tumbleweed because of a cuda error. Don't ask me why */
|
||||
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
|
||||
if(inside_flatpak)
|
||||
system("flatpak-spawn --host -- nvidia-smi -f /dev/null");
|
||||
system("flatpak-spawn --host -- sh -c 'grep -q openSUSE /etc/os-release && nvidia-smi -f /dev/null'");
|
||||
else
|
||||
system("nvidia-smi -f /dev/null");
|
||||
system("sh -c 'grep -q openSUSE /etc/os-release && nvidia-smi -f /dev/null'");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -71,16 +71,15 @@ void gsr_video_encoder_software_stop(gsr_video_encoder_software *self, AVCodecCo
|
||||
}
|
||||
|
||||
static void gsr_video_encoder_software_copy_textures_to_frame(gsr_video_encoder *encoder, AVFrame *frame, gsr_color_conversion *color_conversion) {
|
||||
gsr_video_encoder_software *self = encoder->priv;
|
||||
(void)encoder;
|
||||
//gsr_video_encoder_software *self = encoder->priv;
|
||||
// TODO: hdr support
|
||||
const unsigned int formats[2] = { GL_RED, GL_RG };
|
||||
const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
|
||||
for(int i = 0; i < 2; ++i) {
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[i]);
|
||||
// We could use glGetTexSubImage and then we wouldn't have to use a specific linesize (LINESIZE_ALIGNMENT) that adds padding,
|
||||
// but glGetTexSubImage is only available starting from opengl 4.5.
|
||||
self->params.egl->glGetTexImage(GL_TEXTURE_2D, 0, formats[i], GL_UNSIGNED_BYTE, frame->data[i]);
|
||||
// TODO: Use glPixelStore?
|
||||
gsr_color_conversion_read_destination_texture(color_conversion, i, 0, 0, frame->width / div[i], frame->height / div[i], formats[i], GL_UNSIGNED_BYTE, frame->data[i]);
|
||||
}
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
// cap_kms->kms.base.egl->eglSwapBuffers(cap_kms->kms.base.egl->egl_display, cap_kms->kms.base.egl->egl_surface);
|
||||
|
||||
//self->params.egl->glFlush();
|
||||
|
||||
@@ -92,10 +92,6 @@ static bool gsr_video_encoder_vaapi_setup_textures(gsr_video_encoder_vaapi *self
|
||||
if(self->prime.fourcc == VA_FOURCC_NV12 || self->prime.fourcc == VA_FOURCC_P010) {
|
||||
const uint32_t *formats = self->prime.fourcc == VA_FOURCC_NV12 ? formats_nv12 : formats_p010;
|
||||
const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
|
||||
const float border_colors[2][4] = {
|
||||
{0.0f, 0.0f, 0.0f, 1.0f},
|
||||
{0.5f, 0.5f, 0.0f, 1.0f}
|
||||
};
|
||||
|
||||
self->params.egl->glGenTextures(2, self->target_textures);
|
||||
for(int i = 0; i < 2; ++i) {
|
||||
@@ -125,9 +121,6 @@ static bool gsr_video_encoder_vaapi_setup_textures(gsr_video_encoder_vaapi *self
|
||||
}
|
||||
|
||||
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[i]);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->params.egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_colors[i]);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
self->params.egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
|
||||
@@ -71,11 +71,15 @@ static bool gsr_image_writer_write_opengl_texture_to_file(gsr_image_writer *self
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: hdr support
|
||||
self->egl->glBindTexture(GL_TEXTURE_2D, self->texture);
|
||||
// We could use glGetTexSubImage, but it's only available starting from opengl 4.5
|
||||
self->egl->glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame_data);
|
||||
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
unsigned int fbo = 0;
|
||||
self->egl->glGenFramebuffers(1, &fbo);
|
||||
self->egl->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
self->egl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->texture, 0);
|
||||
|
||||
self->egl->glReadPixels(0, 0, self->width, self->height, GL_RGBA, GL_UNSIGNED_BYTE, frame_data);
|
||||
|
||||
self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
self->egl->glDeleteFramebuffers(1, &fbo);
|
||||
|
||||
self->egl->glFlush();
|
||||
self->egl->glFinish();
|
||||
|
||||
33
src/main.cpp
33
src/main.cpp
@@ -5,7 +5,7 @@ extern "C" {
|
||||
#include "../include/capture/kms.h"
|
||||
#ifdef GSR_PORTAL
|
||||
#include "../include/capture/portal.h"
|
||||
#include "../dbus/client/dbus_client.h"
|
||||
#include "../include/dbus.h"
|
||||
#endif
|
||||
#ifdef GSR_APP_AUDIO
|
||||
#include "../include/pipewire_audio.h"
|
||||
@@ -663,7 +663,6 @@ static void open_video_software(AVCodecContext *codec_context, const args_parser
|
||||
|
||||
av_dict_set(&options, "preset", "veryfast", 0);
|
||||
av_dict_set(&options, "tune", "film", 0);
|
||||
av_dict_set(&options, "profile", "high", 0);
|
||||
|
||||
if(codec_context->codec_id == AV_CODEC_ID_H264) {
|
||||
av_dict_set(&options, "coder", "cabac", 0); // TODO: cavlc is faster than cabac but worse compression. Which to use?
|
||||
@@ -1848,15 +1847,15 @@ static void list_supported_capture_options(const gsr_window *window, const char
|
||||
if(!wayland)
|
||||
return;
|
||||
|
||||
gsr_dbus_client dbus_client;
|
||||
if(!gsr_dbus_client_init(&dbus_client, NULL))
|
||||
gsr_dbus dbus;
|
||||
if(!gsr_dbus_init(&dbus, NULL))
|
||||
return;
|
||||
|
||||
char session_handle[128];
|
||||
if(gsr_dbus_client_screencast_create_session(&dbus_client, session_handle, sizeof(session_handle)) == 0)
|
||||
char *session_handle = NULL;
|
||||
if(gsr_dbus_screencast_create_session(&dbus, &session_handle) == 0)
|
||||
puts("portal");
|
||||
|
||||
gsr_dbus_client_deinit(&dbus_client);
|
||||
gsr_dbus_deinit(&dbus);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3313,7 +3312,7 @@ int main(int argc, char **argv) {
|
||||
int damage_fps_counter = 0;
|
||||
|
||||
bool paused = false;
|
||||
double paused_time_offset = 0.0;
|
||||
std::atomic<double> paused_time_offset(0.0);
|
||||
double paused_time_start = 0.0;
|
||||
bool replay_recording = false;
|
||||
RecordingStartResult replay_recording_start_result;
|
||||
@@ -3629,7 +3628,23 @@ int main(int argc, char **argv) {
|
||||
|
||||
// TODO: Dont do this if no damage?
|
||||
egl.glClear(0);
|
||||
|
||||
bool capture_has_synchronous_task = false;
|
||||
if(capture->capture_has_synchronous_task) {
|
||||
capture_has_synchronous_task = capture->capture_has_synchronous_task(capture);
|
||||
if(capture_has_synchronous_task) {
|
||||
paused_time_start = clock_get_monotonic_seconds();
|
||||
paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
gsr_capture_capture(capture, &capture_metadata, &color_conversion);
|
||||
|
||||
if(capture_has_synchronous_task) {
|
||||
paused_time_offset = paused_time_offset + (clock_get_monotonic_seconds() - paused_time_start);
|
||||
paused = false;
|
||||
}
|
||||
|
||||
gsr_egl_swap_buffers(&egl);
|
||||
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, &color_conversion);
|
||||
|
||||
@@ -3679,7 +3694,7 @@ int main(int argc, char **argv) {
|
||||
paused_time_start = clock_get_monotonic_seconds();
|
||||
fprintf(stderr, "Paused\n");
|
||||
} else {
|
||||
paused_time_offset += (clock_get_monotonic_seconds() - paused_time_start);
|
||||
paused_time_offset = paused_time_offset + (clock_get_monotonic_seconds() - paused_time_start);
|
||||
fprintf(stderr, "Unpaused\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <spa/debug/types.h>
|
||||
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <drm_fourcc.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
@@ -280,13 +280,21 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
|
||||
self->negotiated = true;
|
||||
}
|
||||
|
||||
static void on_state_changed_cb(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) {
|
||||
(void)old;
|
||||
static void on_state_changed_cb(void *user_data, enum pw_stream_state prev_state, enum pw_stream_state new_state, const char *error) {
|
||||
gsr_pipewire_video *self = user_data;
|
||||
|
||||
fprintf(stderr, "gsr info: pipewire: stream %p state: \"%s\" (error: %s)\n",
|
||||
(void*)self->stream, pw_stream_state_as_string(state),
|
||||
fprintf(stderr, "gsr info: pipewire: stream %p previous state: \"%s\", new state: \"%s\" (error: %s)\n",
|
||||
(void*)self->stream, pw_stream_state_as_string(prev_state), pw_stream_state_as_string(new_state),
|
||||
error ? error : "none");
|
||||
|
||||
pthread_mutex_lock(&self->mutex);
|
||||
if(new_state == PW_STREAM_STATE_PAUSED) {
|
||||
self->paused_start_secs = clock_get_monotonic_seconds();
|
||||
self->paused = true;
|
||||
} else {
|
||||
self->paused = false;
|
||||
}
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
}
|
||||
|
||||
static const struct pw_stream_events stream_events = {
|
||||
@@ -346,19 +354,19 @@ static int64_t spa_video_format_to_drm_format(const enum spa_video_format format
|
||||
switch(format) {
|
||||
case SPA_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888;
|
||||
case SPA_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888;
|
||||
case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888;
|
||||
case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888;
|
||||
// case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888;
|
||||
//case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888;
|
||||
case SPA_VIDEO_FORMAT_RGB: return DRM_FORMAT_XBGR8888;
|
||||
case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888;
|
||||
case SPA_VIDEO_FORMAT_ARGB: return DRM_FORMAT_XRGB8888;
|
||||
case SPA_VIDEO_FORMAT_ABGR: return DRM_FORMAT_XRGB8888;
|
||||
//case SPA_VIDEO_FORMAT_ARGB: return DRM_FORMAT_XRGB8888;
|
||||
//case SPA_VIDEO_FORMAT_ABGR: return DRM_FORMAT_XRGB8888;
|
||||
#if PW_CHECK_VERSION(0, 3, 41)
|
||||
case SPA_VIDEO_FORMAT_xRGB_210LE: return DRM_FORMAT_XRGB2101010;
|
||||
case SPA_VIDEO_FORMAT_xBGR_210LE: return DRM_FORMAT_XBGR2101010;
|
||||
case SPA_VIDEO_FORMAT_ARGB_210LE: return DRM_FORMAT_ARGB2101010;
|
||||
case SPA_VIDEO_FORMAT_ABGR_210LE: return DRM_FORMAT_ABGR2101010;
|
||||
// case SPA_VIDEO_FORMAT_ARGB_210LE: return DRM_FORMAT_ARGB2101010;
|
||||
// case SPA_VIDEO_FORMAT_ABGR_210LE: return DRM_FORMAT_ABGR2101010;
|
||||
#endif
|
||||
default: break;
|
||||
default: break;
|
||||
}
|
||||
return DRM_FORMAT_INVALID;
|
||||
}
|
||||
@@ -366,23 +374,23 @@ static int64_t spa_video_format_to_drm_format(const enum spa_video_format format
|
||||
#if PW_CHECK_VERSION(0, 3, 41)
|
||||
#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS
|
||||
#else
|
||||
#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS 8
|
||||
#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS 4
|
||||
#endif
|
||||
|
||||
static const enum spa_video_format video_formats[GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS] = {
|
||||
SPA_VIDEO_FORMAT_BGRA,
|
||||
// SPA_VIDEO_FORMAT_BGRA,
|
||||
SPA_VIDEO_FORMAT_BGRx,
|
||||
SPA_VIDEO_FORMAT_BGR,
|
||||
SPA_VIDEO_FORMAT_RGBx,
|
||||
SPA_VIDEO_FORMAT_RGBA,
|
||||
// SPA_VIDEO_FORMAT_RGBA,
|
||||
SPA_VIDEO_FORMAT_RGB,
|
||||
SPA_VIDEO_FORMAT_ARGB,
|
||||
SPA_VIDEO_FORMAT_ABGR,
|
||||
// SPA_VIDEO_FORMAT_ARGB,
|
||||
// SPA_VIDEO_FORMAT_ABGR,
|
||||
#if PW_CHECK_VERSION(0, 3, 41)
|
||||
SPA_VIDEO_FORMAT_xRGB_210LE,
|
||||
SPA_VIDEO_FORMAT_xBGR_210LE,
|
||||
SPA_VIDEO_FORMAT_ARGB_210LE,
|
||||
SPA_VIDEO_FORMAT_ABGR_210LE
|
||||
// SPA_VIDEO_FORMAT_ARGB_210LE,
|
||||
// SPA_VIDEO_FORMAT_ABGR_210LE
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -471,6 +479,27 @@ static void gsr_pipewire_video_init_modifiers(gsr_pipewire_video *self) {
|
||||
spa_video_format_get_modifiers(self, self->supported_video_formats[i].format, self->modifiers + self->num_modifiers, GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS - self->num_modifiers, &num_modifiers);
|
||||
self->supported_video_formats[i].modifiers_index = self->num_modifiers;
|
||||
self->supported_video_formats[i].modifiers_size = num_modifiers;
|
||||
self->num_modifiers += num_modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_video_format_remove_modifier(gsr_pipewire_video *self, gsr_video_format *video_format, uint64_t modifier) {
|
||||
for(size_t i = 0; i < video_format->modifiers_size; ++i) {
|
||||
if(self->modifiers[video_format->modifiers_index + i] != modifier)
|
||||
continue;
|
||||
|
||||
for(size_t j = i + 1; j < video_format->modifiers_size; ++j) {
|
||||
self->modifiers[j - 1] = self->modifiers[j];
|
||||
}
|
||||
--video_format->modifiers_size;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_video_remove_modifier(gsr_pipewire_video *self, uint64_t modifier) {
|
||||
for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) {
|
||||
gsr_video_format *video_format = &self->supported_video_formats[i];
|
||||
gsr_pipewire_video_format_remove_modifier(self, video_format, modifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,6 +680,7 @@ void gsr_pipewire_video_deinit(gsr_pipewire_video *self) {
|
||||
self->dmabuf_num_planes = 0;
|
||||
|
||||
self->negotiated = false;
|
||||
self->renegotiated = false;
|
||||
|
||||
if(self->mutex_initialized) {
|
||||
pthread_mutex_destroy(&self->mutex);
|
||||
@@ -702,9 +732,19 @@ static EGLImage gsr_pipewire_video_create_egl_image_with_fallback(gsr_pipewire_v
|
||||
} else {
|
||||
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, true);
|
||||
if(!image) {
|
||||
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n");
|
||||
self->no_modifiers_fallback = true;
|
||||
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
|
||||
if(self->renegotiated) {
|
||||
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n");
|
||||
self->no_modifiers_fallback = true;
|
||||
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
|
||||
} else {
|
||||
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, renegotiating with a different modifier\n");
|
||||
self->negotiated = false;
|
||||
self->renegotiated = true;
|
||||
gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier);
|
||||
pw_thread_loop_lock(self->thread_loop);
|
||||
pw_loop_signal_event(pw_thread_loop_get_loop(self->thread_loop), self->reneg);
|
||||
pw_thread_loop_unlock(self->thread_loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
@@ -736,13 +776,9 @@ static void gsr_pipewire_video_update_cursor_texture(gsr_pipewire_video *self, g
|
||||
if(!self->cursor.data)
|
||||
return;
|
||||
|
||||
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
self->egl->glBindTexture(GL_TEXTURE_2D, texture_map.cursor_texture_id);
|
||||
// TODO: glTextureSubImage2D if same size
|
||||
self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, self->cursor.width, self->cursor.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, self->cursor.data);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
@@ -767,12 +803,15 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
|
||||
}
|
||||
|
||||
EGLImage image = gsr_pipewire_video_create_egl_image_with_fallback(self);
|
||||
if(image) {
|
||||
gsr_pipewire_video_bind_image_to_texture_with_fallback(self, texture_map, image);
|
||||
*using_external_image = self->external_texture_fallback;
|
||||
self->egl->eglDestroyImage(self->egl->egl_display, image);
|
||||
if(!image) {
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
gsr_pipewire_video_bind_image_to_texture_with_fallback(self, texture_map, image);
|
||||
*using_external_image = self->external_texture_fallback;
|
||||
self->egl->eglDestroyImage(self->egl->egl_display, image);
|
||||
|
||||
gsr_pipewire_video_update_cursor_texture(self, texture_map);
|
||||
|
||||
region->x = 0;
|
||||
@@ -810,6 +849,9 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
|
||||
}
|
||||
|
||||
bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self) {
|
||||
if(!self->mutex_initialized)
|
||||
return false;
|
||||
|
||||
bool damaged = false;
|
||||
pthread_mutex_lock(&self->mutex);
|
||||
damaged = self->damaged;
|
||||
@@ -818,7 +860,21 @@ bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self) {
|
||||
}
|
||||
|
||||
void gsr_pipewire_video_clear_damage(gsr_pipewire_video *self) {
|
||||
if(!self->mutex_initialized)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&self->mutex);
|
||||
self->damaged = false;
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
}
|
||||
|
||||
bool gsr_pipewire_video_should_restart(gsr_pipewire_video *self) {
|
||||
if(!self->mutex_initialized)
|
||||
return false;
|
||||
|
||||
bool should_restart = false;
|
||||
pthread_mutex_lock(&self->mutex);
|
||||
should_restart = self->paused && clock_get_monotonic_seconds() - self->paused_start_secs >= 3.0;
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
return should_restart;
|
||||
}
|
||||
|
||||
14
src/utils.c
14
src/utils.c
@@ -598,22 +598,12 @@ vec2i scale_keep_aspect_ratio(vec2i from, vec2i to) {
|
||||
}
|
||||
|
||||
unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format, int filter) {
|
||||
float border_color[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
if(format == GL_RG) { // UV
|
||||
border_color[0] = 0.5f;
|
||||
border_color[1] = 0.5f;
|
||||
border_color[2] = 0.0f;
|
||||
border_color[3] = 1.0f;
|
||||
}
|
||||
|
||||
unsigned int texture_id = 0;
|
||||
egl->glGenTextures(1, &texture_id);
|
||||
egl->glBindTexture(GL_TEXTURE_2D, texture_id);
|
||||
egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
|
||||
//egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
|
||||
egl->glTexStorage2D(GL_TEXTURE_2D, 1, internal_format, width, height);
|
||||
|
||||
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
|
||||
|
||||
@@ -85,10 +85,6 @@ int window_texture_on_resize(WindowTexture *self) {
|
||||
texture_id = self->texture_id;
|
||||
}
|
||||
|
||||
const float border_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
self->egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user