optimization

This commit is contained in:
2026-07-18 10:06:53 +05:00
parent 7d91df3c6b
commit 2c166a40e3
7 changed files with 108 additions and 121 deletions

View File

@@ -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);