optimization
This commit is contained in:
182
src/renderer.c
182
src/renderer.c
@@ -10,8 +10,9 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
@@ -22,8 +23,6 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "protocol.h"
|
||||
@@ -41,7 +40,6 @@ struct renderer {
|
||||
struct config config;
|
||||
GtkWidget *window;
|
||||
WebKitWebView *web_view;
|
||||
GPid xvfb_pid;
|
||||
struct owb_shared_frames *shared;
|
||||
size_t shared_size;
|
||||
uint64_t next_sequence;
|
||||
@@ -88,9 +86,8 @@ static bool read_config(struct config *config)
|
||||
config->height = header.height;
|
||||
config->fps = header.fps;
|
||||
config->transparent = header.transparent != 0;
|
||||
/* Protocol v2 used this flag for an experimental host-display mode. It is
|
||||
* intentionally ignored: an OBS source must never create an input window on
|
||||
* the user's desktop. */
|
||||
/* Protocol v2 retains this legacy flag, but this renderer always enables the
|
||||
* WebKit compositor. */
|
||||
(void)header.hardware_acceleration;
|
||||
config->url = calloc((size_t)header.url_length + 1, 1);
|
||||
config->css = calloc((size_t)header.css_length + 1, 1);
|
||||
@@ -119,64 +116,46 @@ static char *normalize_uri(const char *value)
|
||||
return uri;
|
||||
}
|
||||
|
||||
static bool prepare_display(struct renderer *renderer)
|
||||
static void prepare_webkit(void)
|
||||
{
|
||||
char screen[64];
|
||||
g_snprintf(screen, sizeof(screen), "%ux%ux24", renderer->config.width, renderer->config.height);
|
||||
char *argv[] = {"Xvfb", "-displayfd", "1", "-screen", "0", screen, "+extension", "Composite",
|
||||
"-nolisten", "tcp", "-noreset", "-dpi", "96", NULL};
|
||||
int stdout_fd = -1;
|
||||
GError *error = NULL;
|
||||
if (!g_spawn_async_with_pipes(NULL, argv, NULL,
|
||||
G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
|
||||
&renderer->xvfb_pid, NULL, &stdout_fd, NULL, &error)) {
|
||||
fprintf(stderr, "Cannot start Xvfb: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return false;
|
||||
}
|
||||
char display_number[32] = {0};
|
||||
size_t display_used = 0;
|
||||
while (display_used + 1 < sizeof(display_number) &&
|
||||
!memchr(display_number, '\n', display_used)) {
|
||||
struct pollfd display_poll = {.fd = stdout_fd, .events = POLLIN};
|
||||
int ready;
|
||||
do {
|
||||
ready = poll(&display_poll, 1, 10000);
|
||||
} while (ready < 0 && errno == EINTR);
|
||||
if (ready <= 0)
|
||||
break;
|
||||
ssize_t count = read(stdout_fd, display_number + display_used,
|
||||
sizeof(display_number) - display_used - 1);
|
||||
if (count <= 0)
|
||||
break;
|
||||
display_used += (size_t)count;
|
||||
}
|
||||
close(stdout_fd);
|
||||
if (!display_used || !memchr(display_number, '\n', display_used)) {
|
||||
fprintf(stderr, "Xvfb did not publish a display number within 10 seconds\n");
|
||||
kill(renderer->xvfb_pid, SIGTERM);
|
||||
waitpid(renderer->xvfb_pid, NULL, 0);
|
||||
g_spawn_close_pid(renderer->xvfb_pid);
|
||||
renderer->xvfb_pid = 0;
|
||||
return false;
|
||||
}
|
||||
display_number[strcspn(display_number, "\r\n")] = '\0';
|
||||
char *display = g_strdup_printf(":%s", display_number);
|
||||
g_setenv("DISPLAY", display, TRUE);
|
||||
/* Reuse OBS' X11 or Wayland connection. WebKitGTK's DMA-BUF backing store
|
||||
* needs a native GdkWindow to create the GL context used to import GPU
|
||||
* buffers. The window is made transparent and non-interactive below. */
|
||||
g_setenv("GDK_SCALE", "1", TRUE);
|
||||
g_setenv("GDK_DPI_SCALE", "1", TRUE);
|
||||
g_setenv("GDK_BACKEND", "x11", TRUE);
|
||||
g_free(display);
|
||||
/* WebKitGTK's accelerated backing store renders into a DRM DMA-BUF and maps
|
||||
* the completed buffer into this private X display. Xvfb remains useful for
|
||||
* keeping the Gtk window off the desktop; it must not force WebKit itself to
|
||||
* use llvmpipe. Override software-rendering flags inherited from OBS too. */
|
||||
g_unsetenv("LIBGL_ALWAYS_SOFTWARE");
|
||||
g_unsetenv("GALLIUM_DRIVER");
|
||||
g_unsetenv("MESA_LOADER_DRIVER_OVERRIDE");
|
||||
g_unsetenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM");
|
||||
g_unsetenv("WEBKIT_DMABUF_RENDERER_DISABLE_GBM");
|
||||
g_setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "0", TRUE);
|
||||
g_setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "0", TRUE);
|
||||
g_setenv("WEBKIT_FORCE_COMPOSITING_MODE", "1", TRUE);
|
||||
g_setenv("NO_AT_BRIDGE", "1", FALSE);
|
||||
return true;
|
||||
g_setenv("NO_AT_BRIDGE", "1", TRUE);
|
||||
}
|
||||
|
||||
static bool process_uses_drm_render_node(void)
|
||||
{
|
||||
DIR *directory = opendir("/proc/self/fd");
|
||||
if (!directory)
|
||||
return false;
|
||||
bool found = false;
|
||||
struct dirent *entry;
|
||||
while (!found && (entry = readdir(directory))) {
|
||||
if (entry->d_name[0] == '.')
|
||||
continue;
|
||||
char descriptor[64];
|
||||
char target[PATH_MAX];
|
||||
g_snprintf(descriptor, sizeof(descriptor), "/proc/self/fd/%s", entry->d_name);
|
||||
ssize_t length = readlink(descriptor, target, sizeof(target) - 1);
|
||||
if (length <= 0)
|
||||
continue;
|
||||
target[length] = '\0';
|
||||
found = g_str_has_prefix(target, "/dev/dri/renderD");
|
||||
}
|
||||
closedir(directory);
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool map_shared_frames(struct renderer *renderer)
|
||||
@@ -206,35 +185,29 @@ static bool initialize_x11_capture(struct renderer *renderer)
|
||||
if (!XDamageQueryExtension(renderer->capture_display, &renderer->damage_event_base,
|
||||
&damage_error_base))
|
||||
return false;
|
||||
int composite_event_base = 0;
|
||||
int composite_error_base = 0;
|
||||
if (!XCompositeQueryExtension(renderer->capture_display, &composite_event_base,
|
||||
&composite_error_base))
|
||||
return false;
|
||||
|
||||
/* An opaque WebKit view may render into child/compositor surfaces which are
|
||||
* absent from the top-level window's named backing pixmap. Xvfb's root
|
||||
* drawable contains the final assembled image, and the renderer window
|
||||
* covers that private screen exactly. */
|
||||
if (!renderer->config.transparent) {
|
||||
renderer->capture_window = DefaultRootWindow(renderer->capture_display);
|
||||
renderer->capture_pixmap = renderer->capture_window;
|
||||
} else {
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(renderer->window);
|
||||
if (!gdk_window || !GDK_IS_X11_WINDOW(gdk_window))
|
||||
return false;
|
||||
renderer->capture_window = GDK_WINDOW_XID(gdk_window);
|
||||
int event_base = 0, error_base = 0;
|
||||
if (!XCompositeQueryExtension(renderer->capture_display, &event_base, &error_base))
|
||||
return false;
|
||||
XCompositeRedirectWindow(renderer->capture_display, renderer->capture_window,
|
||||
CompositeRedirectManual);
|
||||
renderer->capture_redirected = true;
|
||||
XSync(renderer->capture_display, False);
|
||||
renderer->capture_pixmap = XCompositeNameWindowPixmap(renderer->capture_display,
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(renderer->window);
|
||||
if (!gdk_window || !GDK_IS_X11_WINDOW(gdk_window))
|
||||
return false;
|
||||
renderer->capture_window = GDK_WINDOW_XID(gdk_window);
|
||||
XCompositeRedirectWindow(renderer->capture_display, renderer->capture_window,
|
||||
CompositeRedirectManual);
|
||||
renderer->capture_redirected = true;
|
||||
XSync(renderer->capture_display, False);
|
||||
renderer->capture_pixmap = XCompositeNameWindowPixmap(renderer->capture_display,
|
||||
renderer->capture_window);
|
||||
if (!renderer->capture_pixmap)
|
||||
return false;
|
||||
}
|
||||
if (!renderer->capture_pixmap)
|
||||
return false;
|
||||
renderer->capture_damage = XDamageCreate(renderer->capture_display,
|
||||
renderer->capture_window, XDamageReportNonEmpty);
|
||||
if (!renderer->capture_damage)
|
||||
return false;
|
||||
|
||||
XWindowAttributes attributes;
|
||||
if (!XGetWindowAttributes(renderer->capture_display, renderer->capture_window, &attributes))
|
||||
return false;
|
||||
@@ -282,7 +255,7 @@ static void destroy_x11_capture(struct renderer *renderer)
|
||||
shmdt(renderer->capture_shm.shmaddr);
|
||||
if (renderer->capture_shm.shmid >= 0 && !renderer->capture_attached)
|
||||
shmctl(renderer->capture_shm.shmid, IPC_RMID, NULL);
|
||||
if (renderer->capture_pixmap && renderer->capture_redirected)
|
||||
if (renderer->capture_pixmap)
|
||||
XFreePixmap(renderer->capture_display, renderer->capture_pixmap);
|
||||
if (renderer->capture_redirected)
|
||||
XCompositeUnredirectWindow(renderer->capture_display, renderer->capture_window,
|
||||
@@ -330,14 +303,15 @@ static void publish_frame(struct renderer *renderer, const uint8_t *pixels, int
|
||||
(void)ignored;
|
||||
}
|
||||
|
||||
static void publish_x11_frame(struct renderer *renderer)
|
||||
static bool publish_x11_frame(struct renderer *renderer)
|
||||
{
|
||||
if (!XShmGetImage(renderer->capture_display, renderer->capture_pixmap,
|
||||
renderer->capture_image, 0, 0, AllPlanes))
|
||||
return;
|
||||
return false;
|
||||
publish_frame(renderer, (const uint8_t *)renderer->capture_image->data,
|
||||
renderer->capture_image->bytes_per_line,
|
||||
renderer->config.transparent && renderer->capture_image->depth == 32);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool capture_was_damaged(struct renderer *renderer)
|
||||
@@ -368,14 +342,19 @@ static void *capture_loop(void *opaque)
|
||||
struct timespec deadline;
|
||||
clock_gettime(CLOCK_MONOTONIC, &deadline);
|
||||
bool first_frame = true;
|
||||
bool transport_reported = false;
|
||||
while (!atomic_load_explicit(&renderer->stopping, memory_order_acquire)) {
|
||||
bool damaged = capture_was_damaged(renderer);
|
||||
if (first_frame || damaged) {
|
||||
/* Clear the accumulated region before capture. Changes arriving while
|
||||
* XShmGetImage runs then produce a fresh notification for the next tick. */
|
||||
/* Clear before capture so updates arriving during XShmGetImage
|
||||
* produce a fresh notification for the next tick. */
|
||||
XDamageSubtract(renderer->capture_display, renderer->capture_damage,
|
||||
None, None);
|
||||
publish_x11_frame(renderer);
|
||||
bool published = publish_x11_frame(renderer);
|
||||
if (published && !transport_reported && process_uses_drm_render_node()) {
|
||||
fprintf(stderr, "DRM render node: active\n");
|
||||
transport_reported = true;
|
||||
}
|
||||
first_frame = false;
|
||||
}
|
||||
advance_deadline(&deadline, interval_ns);
|
||||
@@ -573,14 +552,13 @@ int main(void)
|
||||
fprintf(stderr, "Invalid renderer configuration\n");
|
||||
return 2;
|
||||
}
|
||||
if (!prepare_display(&renderer))
|
||||
return 3;
|
||||
prepare_webkit();
|
||||
if (!map_shared_frames(&renderer)) {
|
||||
fprintf(stderr, "Cannot map shared frame transport\n");
|
||||
return 4;
|
||||
}
|
||||
if (!gtk_init_check(NULL, NULL)) {
|
||||
fprintf(stderr, "GTK could not connect to private Xvfb display\n");
|
||||
fprintf(stderr, "GTK could not connect to the OBS display\n");
|
||||
return 5;
|
||||
}
|
||||
|
||||
@@ -651,17 +629,20 @@ int main(void)
|
||||
gtk_container_add(GTK_CONTAINER(renderer.window), GTK_WIDGET(renderer.web_view));
|
||||
gtk_widget_realize(renderer.window);
|
||||
GdkWindow *renderer_window = gtk_widget_get_window(renderer.window);
|
||||
if (renderer_window) {
|
||||
cairo_region_t *empty_input = cairo_region_create();
|
||||
gdk_window_input_shape_combine_region(renderer_window, empty_input, 0, 0);
|
||||
cairo_region_destroy(empty_input);
|
||||
if (!renderer_window || gdk_window_get_window_type(renderer_window) == GDK_WINDOW_OFFSCREEN) {
|
||||
fprintf(stderr, "GTK did not create the native window required for GPU rendering\n");
|
||||
return 6;
|
||||
}
|
||||
cairo_region_t *empty_input = cairo_region_create();
|
||||
gdk_window_input_shape_combine_region(renderer_window, empty_input, 0, 0);
|
||||
cairo_region_destroy(empty_input);
|
||||
gdk_window_set_opacity(renderer_window, 0.0);
|
||||
gtk_widget_show_all(renderer.window);
|
||||
while (gtk_events_pending())
|
||||
gtk_main_iteration();
|
||||
if (!initialize_x11_capture(&renderer)) {
|
||||
fprintf(stderr, "Cannot initialize XComposite/XDamage/XShm capture\n");
|
||||
return 6;
|
||||
fprintf(stderr, "Cannot initialize XComposite/XDamage/XShm GPU capture\n");
|
||||
return 7;
|
||||
}
|
||||
|
||||
int output_flags = fcntl(STDOUT_FILENO, F_GETFL, 0);
|
||||
@@ -676,11 +657,11 @@ int main(void)
|
||||
webkit_web_view_load_uri(renderer.web_view, uri);
|
||||
g_free(uri);
|
||||
if (pthread_create(&renderer.capture_thread, NULL, capture_loop, &renderer) != 0) {
|
||||
fprintf(stderr, "Cannot start X11 capture thread\n");
|
||||
return 7;
|
||||
fprintf(stderr, "Cannot start GPU capture thread\n");
|
||||
return 8;
|
||||
}
|
||||
renderer.capture_thread_started = true;
|
||||
fprintf(stderr, "Ready: WebKitGTK %u.%u.%u, %ux%u@%u, GPU compositing, DMA-BUF, X11 capture\n",
|
||||
fprintf(stderr, "Ready: WebKitGTK %u.%u.%u, %ux%u@%u, GPU DMA-BUF, XComposite/XShm capture\n",
|
||||
webkit_get_major_version(),
|
||||
webkit_get_minor_version(), webkit_get_micro_version(), renderer.config.width,
|
||||
renderer.config.height, renderer.config.fps);
|
||||
@@ -693,11 +674,6 @@ int main(void)
|
||||
if (renderer.capture_thread_started)
|
||||
pthread_join(renderer.capture_thread, NULL);
|
||||
destroy_x11_capture(&renderer);
|
||||
if (renderer.xvfb_pid > 0) {
|
||||
kill(renderer.xvfb_pid, SIGTERM);
|
||||
waitpid(renderer.xvfb_pid, NULL, 0);
|
||||
g_spawn_close_pid(renderer.xvfb_pid);
|
||||
}
|
||||
munmap(renderer.shared, renderer.shared_size);
|
||||
free(renderer.config.url);
|
||||
free(renderer.config.css);
|
||||
|
||||
Reference in New Issue
Block a user