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

@@ -62,11 +62,11 @@ if(BUILD_TESTING)
target_include_directories(renderer-smoke-test PRIVATE src) target_include_directories(renderer-smoke-test PRIVATE src)
add_test(NAME renderer-smoke-test add_test(NAME renderer-smoke-test
COMMAND renderer-smoke-test $<TARGET_FILE:obs-webkit-renderer>) COMMAND renderer-smoke-test $<TARGET_FILE:obs-webkit-renderer>)
set_tests_properties(renderer-smoke-test PROPERTIES TIMEOUT 20) set_tests_properties(renderer-smoke-test PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77)
add_test(NAME renderer-opaque-smoke-test add_test(NAME renderer-opaque-smoke-test
COMMAND renderer-smoke-test $<TARGET_FILE:obs-webkit-renderer> opaque) COMMAND renderer-smoke-test $<TARGET_FILE:obs-webkit-renderer> opaque)
set_tests_properties(renderer-opaque-smoke-test PROPERTIES TIMEOUT 20) set_tests_properties(renderer-opaque-smoke-test PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77)
add_executable(renderer-fps-test tests/renderer-fps-test.c) add_executable(renderer-fps-test tests/renderer-fps-test.c)
target_compile_features(renderer-fps-test PRIVATE c_std_11) target_compile_features(renderer-fps-test PRIVATE c_std_11)
@@ -74,7 +74,7 @@ if(BUILD_TESTING)
target_include_directories(renderer-fps-test PRIVATE src) target_include_directories(renderer-fps-test PRIVATE src)
add_test(NAME blur-transition-fps-test add_test(NAME blur-transition-fps-test
COMMAND renderer-fps-test $<TARGET_FILE:obs-webkit-renderer>) COMMAND renderer-fps-test $<TARGET_FILE:obs-webkit-renderer>)
set_tests_properties(blur-transition-fps-test PROPERTIES TIMEOUT 20) set_tests_properties(blur-transition-fps-test PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77)
add_executable(module-load-test tests/module-load-test.c) add_executable(module-load-test tests/module-load-test.c)
target_compile_features(module-load-test PRIVATE c_std_11) target_compile_features(module-load-test PRIVATE c_std_11)
@@ -82,5 +82,5 @@ if(BUILD_TESTING)
add_test(NAME module-load-test COMMAND module-load-test add_test(NAME module-load-test COMMAND module-load-test
$<TARGET_FILE:obs-webkit-browser> "${CMAKE_CURRENT_SOURCE_DIR}/data" $<TARGET_FILE:obs-webkit-browser> "${CMAKE_CURRENT_SOURCE_DIR}/data"
$<TARGET_FILE:obs-webkit-renderer>) $<TARGET_FILE:obs-webkit-renderer>)
set_tests_properties(module-load-test PROPERTIES TIMEOUT 20) set_tests_properties(module-load-test PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77)
endif() endif()

View File

@@ -1,8 +1,11 @@
# OBS WebKit Browser Source (без CEF) # OBS WebKit Browser Source (без CEF)
Нативный input source для OBS Studio на Alpine Linux. Страницы рендерит системный Нативный input source для OBS Studio на Alpine Linux. Страницы рендерит системный
WebKitGTK 4.1 в отдельном процессе на приватном Xvfb-дисплее; Chromium/CEF в проекте WebKitGTK 4.1 в отдельном процессе; Chromium/CEF и Xvfb в проекте нет. Renderer
нет. Кадры передаются в OBS напрямую как BGRA, без FFmpeg и захвата рабочего стола. использует X11/XWayland-сессию OBS и держит WebView в полностью прозрачном
неинтерактивном нативном окне. Нативная surface нужна WebKitGTK для импорта GPU
DMA-BUF. Кадры передаются в OBS напрямую как BGRA, без FFmpeg и захвата рабочего
стола.
Поддерживается: Поддерживается:
@@ -10,18 +13,17 @@ WebKitGTK 4.1 в отдельном процессе на приватном Xvf
- прозрачный фон с alpha-каналом; - прозрачный фон с alpha-каналом;
- пользовательский CSS; - пользовательский CSS;
- размер 28192 px и 160 FPS; - размер 28192 px и 160 FPS;
- аппаратный WebKit compositor через DRM/DMA-BUF (Mesa `radeonsi` на AMD), - аппаратный WebKit compositor через DRM/DMA-BUF (Mesa `radeonsi` на AMD);
при этом Xvfb используется только как изолированный невидимый дисплей; - прямой XComposite/XShm-захват скомпозированного GPU-кадра с alpha;
- прямой XShm-захват итогового кадра Xvfb для непрозрачных страниц и - XDamage-пропуск чтения backing pixmap, пока содержимое страницы не меняется;
XComposite/XShm-захват с alpha для прозрачных;
- XDamage-пропуск чтения framebuffer, пока содержимое страницы не меняется;
- shared-memory транспорт кадров без многомегабайтной записи в pipe; - shared-memory транспорт кадров без многомегабайтной записи в pipe;
- мышь, прокрутка, фокус и базовый ввод с клавиатуры через окно Interact OBS; - мышь, прокрутка, фокус и базовый ввод с клавиатуры через окно Interact OBS;
- перезагрузка страницы и остановка renderer-процесса у скрытого source. - перезагрузка страницы и остановка renderer-процесса у скрытого source.
Звук страницы намеренно выключен: добавьте в OBS отдельный Application Audio Capture. Звук страницы намеренно выключен: добавьте в OBS отдельный Application Audio Capture.
Renderer всегда использует отдельный приватный Xvfb-дисплей и не создаёт окон на Renderer наследует X11/XWayland-дисплей OBS; его нативное окно имеет нулевую opacity,
рабочем столе. WebKit принудительно включает аппаратный compositor и выбирает доступный пустую input shape и не появляется на рабочем столе. Дополнительный display server не
запускается. WebKit принудительно включает аппаратный compositor и выбирает доступный
DRM render node; для него нужен доступ к `/dev/dri/renderD*`. DRM render node; для него нужен доступ к `/dev/dri/renderD*`.
Для тяжёлого полноэкранного `filter: blur()` всё ещё разумно выбирать реальный размер Для тяжёлого полноэкранного `filter: blur()` всё ещё разумно выбирать реальный размер
виджета вместо рендера 4K/1080p с последующим уменьшением в сцене. Пользовательские виджета вместо рендера 4K/1080p с последующим уменьшением в сцене. Пользовательские

View File

@@ -18,4 +18,4 @@ $ROOT apk add \
gtk+3.0 gtk+3.0-dev \ gtk+3.0 gtk+3.0-dev \
webkit2gtk-4.1 webkit2gtk-4.1-dev \ webkit2gtk-4.1 webkit2gtk-4.1-dev \
libx11-dev libxcomposite-dev libxdamage-dev libxext-dev \ libx11-dev libxcomposite-dev libxdamage-dev libxext-dev \
xvfb mesa-dri-gallium mesa-dri-gallium

View File

@@ -10,8 +10,9 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <dirent.h>
#include <limits.h>
#include <pthread.h> #include <pthread.h>
#include <poll.h>
#include <signal.h> #include <signal.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdint.h> #include <stdint.h>
@@ -22,8 +23,6 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/ipc.h> #include <sys/ipc.h>
#include <sys/shm.h> #include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "protocol.h" #include "protocol.h"
@@ -41,7 +40,6 @@ struct renderer {
struct config config; struct config config;
GtkWidget *window; GtkWidget *window;
WebKitWebView *web_view; WebKitWebView *web_view;
GPid xvfb_pid;
struct owb_shared_frames *shared; struct owb_shared_frames *shared;
size_t shared_size; size_t shared_size;
uint64_t next_sequence; uint64_t next_sequence;
@@ -88,9 +86,8 @@ static bool read_config(struct config *config)
config->height = header.height; config->height = header.height;
config->fps = header.fps; config->fps = header.fps;
config->transparent = header.transparent != 0; config->transparent = header.transparent != 0;
/* Protocol v2 used this flag for an experimental host-display mode. It is /* Protocol v2 retains this legacy flag, but this renderer always enables the
* intentionally ignored: an OBS source must never create an input window on * WebKit compositor. */
* the user's desktop. */
(void)header.hardware_acceleration; (void)header.hardware_acceleration;
config->url = calloc((size_t)header.url_length + 1, 1); config->url = calloc((size_t)header.url_length + 1, 1);
config->css = calloc((size_t)header.css_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; return uri;
} }
static bool prepare_display(struct renderer *renderer) static void prepare_webkit(void)
{ {
char screen[64]; /* Reuse OBS' X11 or Wayland connection. WebKitGTK's DMA-BUF backing store
g_snprintf(screen, sizeof(screen), "%ux%ux24", renderer->config.width, renderer->config.height); * needs a native GdkWindow to create the GL context used to import GPU
char *argv[] = {"Xvfb", "-displayfd", "1", "-screen", "0", screen, "+extension", "Composite", * buffers. The window is made transparent and non-interactive below. */
"-nolisten", "tcp", "-noreset", "-dpi", "96", NULL}; g_setenv("GDK_SCALE", "1", TRUE);
int stdout_fd = -1; g_setenv("GDK_DPI_SCALE", "1", TRUE);
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);
g_setenv("GDK_BACKEND", "x11", 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("LIBGL_ALWAYS_SOFTWARE");
g_unsetenv("GALLIUM_DRIVER"); g_unsetenv("GALLIUM_DRIVER");
g_unsetenv("MESA_LOADER_DRIVER_OVERRIDE"); 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_COMPOSITING_MODE", "0", TRUE);
g_setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "0", TRUE); g_setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "0", TRUE);
g_setenv("WEBKIT_FORCE_COMPOSITING_MODE", "1", TRUE); g_setenv("WEBKIT_FORCE_COMPOSITING_MODE", "1", TRUE);
g_setenv("NO_AT_BRIDGE", "1", FALSE); g_setenv("NO_AT_BRIDGE", "1", TRUE);
return 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) 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, if (!XDamageQueryExtension(renderer->capture_display, &renderer->damage_event_base,
&damage_error_base)) &damage_error_base))
return false; 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 GdkWindow *gdk_window = gtk_widget_get_window(renderer->window);
* absent from the top-level window's named backing pixmap. Xvfb's root if (!gdk_window || !GDK_IS_X11_WINDOW(gdk_window))
* drawable contains the final assembled image, and the renderer window return false;
* covers that private screen exactly. */ renderer->capture_window = GDK_WINDOW_XID(gdk_window);
if (!renderer->config.transparent) { XCompositeRedirectWindow(renderer->capture_display, renderer->capture_window,
renderer->capture_window = DefaultRootWindow(renderer->capture_display); CompositeRedirectManual);
renderer->capture_pixmap = renderer->capture_window; renderer->capture_redirected = true;
} else { XSync(renderer->capture_display, False);
GdkWindow *gdk_window = gtk_widget_get_window(renderer->window); renderer->capture_pixmap = XCompositeNameWindowPixmap(renderer->capture_display,
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,
renderer->capture_window); renderer->capture_window);
if (!renderer->capture_pixmap) if (!renderer->capture_pixmap)
return false; return false;
}
renderer->capture_damage = XDamageCreate(renderer->capture_display, renderer->capture_damage = XDamageCreate(renderer->capture_display,
renderer->capture_window, XDamageReportNonEmpty); renderer->capture_window, XDamageReportNonEmpty);
if (!renderer->capture_damage) if (!renderer->capture_damage)
return false; return false;
XWindowAttributes attributes; XWindowAttributes attributes;
if (!XGetWindowAttributes(renderer->capture_display, renderer->capture_window, &attributes)) if (!XGetWindowAttributes(renderer->capture_display, renderer->capture_window, &attributes))
return false; return false;
@@ -282,7 +255,7 @@ static void destroy_x11_capture(struct renderer *renderer)
shmdt(renderer->capture_shm.shmaddr); shmdt(renderer->capture_shm.shmaddr);
if (renderer->capture_shm.shmid >= 0 && !renderer->capture_attached) if (renderer->capture_shm.shmid >= 0 && !renderer->capture_attached)
shmctl(renderer->capture_shm.shmid, IPC_RMID, NULL); 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); XFreePixmap(renderer->capture_display, renderer->capture_pixmap);
if (renderer->capture_redirected) if (renderer->capture_redirected)
XCompositeUnredirectWindow(renderer->capture_display, renderer->capture_window, 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; (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, if (!XShmGetImage(renderer->capture_display, renderer->capture_pixmap,
renderer->capture_image, 0, 0, AllPlanes)) renderer->capture_image, 0, 0, AllPlanes))
return; return false;
publish_frame(renderer, (const uint8_t *)renderer->capture_image->data, publish_frame(renderer, (const uint8_t *)renderer->capture_image->data,
renderer->capture_image->bytes_per_line, renderer->capture_image->bytes_per_line,
renderer->config.transparent && renderer->capture_image->depth == 32); renderer->config.transparent && renderer->capture_image->depth == 32);
return true;
} }
static bool capture_was_damaged(struct renderer *renderer) static bool capture_was_damaged(struct renderer *renderer)
@@ -368,14 +342,19 @@ static void *capture_loop(void *opaque)
struct timespec deadline; struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline); clock_gettime(CLOCK_MONOTONIC, &deadline);
bool first_frame = true; bool first_frame = true;
bool transport_reported = false;
while (!atomic_load_explicit(&renderer->stopping, memory_order_acquire)) { while (!atomic_load_explicit(&renderer->stopping, memory_order_acquire)) {
bool damaged = capture_was_damaged(renderer); bool damaged = capture_was_damaged(renderer);
if (first_frame || damaged) { if (first_frame || damaged) {
/* Clear the accumulated region before capture. Changes arriving while /* Clear before capture so updates arriving during XShmGetImage
* XShmGetImage runs then produce a fresh notification for the next tick. */ * produce a fresh notification for the next tick. */
XDamageSubtract(renderer->capture_display, renderer->capture_damage, XDamageSubtract(renderer->capture_display, renderer->capture_damage,
None, None); 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; first_frame = false;
} }
advance_deadline(&deadline, interval_ns); advance_deadline(&deadline, interval_ns);
@@ -573,14 +552,13 @@ int main(void)
fprintf(stderr, "Invalid renderer configuration\n"); fprintf(stderr, "Invalid renderer configuration\n");
return 2; return 2;
} }
if (!prepare_display(&renderer)) prepare_webkit();
return 3;
if (!map_shared_frames(&renderer)) { if (!map_shared_frames(&renderer)) {
fprintf(stderr, "Cannot map shared frame transport\n"); fprintf(stderr, "Cannot map shared frame transport\n");
return 4; return 4;
} }
if (!gtk_init_check(NULL, NULL)) { 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; return 5;
} }
@@ -651,17 +629,20 @@ int main(void)
gtk_container_add(GTK_CONTAINER(renderer.window), GTK_WIDGET(renderer.web_view)); gtk_container_add(GTK_CONTAINER(renderer.window), GTK_WIDGET(renderer.web_view));
gtk_widget_realize(renderer.window); gtk_widget_realize(renderer.window);
GdkWindow *renderer_window = gtk_widget_get_window(renderer.window); GdkWindow *renderer_window = gtk_widget_get_window(renderer.window);
if (renderer_window) { if (!renderer_window || gdk_window_get_window_type(renderer_window) == GDK_WINDOW_OFFSCREEN) {
cairo_region_t *empty_input = cairo_region_create(); fprintf(stderr, "GTK did not create the native window required for GPU rendering\n");
gdk_window_input_shape_combine_region(renderer_window, empty_input, 0, 0); return 6;
cairo_region_destroy(empty_input);
} }
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); gtk_widget_show_all(renderer.window);
while (gtk_events_pending()) while (gtk_events_pending())
gtk_main_iteration(); gtk_main_iteration();
if (!initialize_x11_capture(&renderer)) { if (!initialize_x11_capture(&renderer)) {
fprintf(stderr, "Cannot initialize XComposite/XDamage/XShm capture\n"); fprintf(stderr, "Cannot initialize XComposite/XDamage/XShm GPU capture\n");
return 6; return 7;
} }
int output_flags = fcntl(STDOUT_FILENO, F_GETFL, 0); 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); webkit_web_view_load_uri(renderer.web_view, uri);
g_free(uri); g_free(uri);
if (pthread_create(&renderer.capture_thread, NULL, capture_loop, &renderer) != 0) { if (pthread_create(&renderer.capture_thread, NULL, capture_loop, &renderer) != 0) {
fprintf(stderr, "Cannot start X11 capture thread\n"); fprintf(stderr, "Cannot start GPU capture thread\n");
return 7; return 8;
} }
renderer.capture_thread_started = true; 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_major_version(),
webkit_get_minor_version(), webkit_get_micro_version(), renderer.config.width, webkit_get_minor_version(), webkit_get_micro_version(), renderer.config.width,
renderer.config.height, renderer.config.fps); renderer.config.height, renderer.config.fps);
@@ -693,11 +674,6 @@ int main(void)
if (renderer.capture_thread_started) if (renderer.capture_thread_started)
pthread_join(renderer.capture_thread, NULL); pthread_join(renderer.capture_thread, NULL);
destroy_x11_capture(&renderer); 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); munmap(renderer.shared, renderer.shared_size);
free(renderer.config.url); free(renderer.config.url);
free(renderer.config.css); free(renderer.config.css);

View File

@@ -29,7 +29,10 @@ int main(int argc, char **argv)
} }
if (!obs_startup("en-US", NULL, NULL)) { if (!obs_startup("en-US", NULL, NULL)) {
fprintf(stderr, "obs_startup failed\n"); fprintf(stderr, "obs_startup failed\n");
return 1; unlink(renderer_link);
unlink(locale_link);
rmdir(temporary);
return 77;
} }
obs_module_t *module = NULL; obs_module_t *module = NULL;
int result = obs_open_module(&module, argv[1], temporary); int result = obs_open_module(&module, argv[1], temporary);

View File

@@ -169,8 +169,11 @@ int main(int argc, char **argv)
close(control[0]); close(control[0]);
close(frames[0]); close(frames[0]);
kill(child, SIGTERM); kill(child, SIGTERM);
while (waitpid(child, NULL, 0) < 0 && errno == EINTR) { int child_status = 0;
while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {
} }
if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 5)
return 77;
return frames_read && fps >= requested_fps * 0.80 && changed_fps >= requested_fps * 0.50 return frames_read && fps >= requested_fps * 0.80 && changed_fps >= requested_fps * 0.50
? 0 ? 0
: 1; : 1;

View File

@@ -117,7 +117,7 @@ int main(int argc, char **argv)
.height = height, .height = height,
.fps = 10, .fps = 10,
.transparent = opaque ? 0 : 1, .transparent = opaque ? 0 : 1,
/* GPU rendering must still use the private Xvfb and produce a frame. */ /* GPU rendering must still produce an invisible captured frame. */
.hardware_acceleration = 1, .hardware_acceleration = 1,
.url_length = (uint32_t)strlen(url), .url_length = (uint32_t)strlen(url),
.css_length = (uint32_t)strlen(css), .css_length = (uint32_t)strlen(css),
@@ -179,8 +179,11 @@ int main(int argc, char **argv)
munmap(shared, shared_size); munmap(shared, shared_size);
close(shared_fd); close(shared_fd);
kill(child, SIGTERM); kill(child, SIGTERM);
while (waitpid(child, NULL, 0) < 0 && errno == EINTR) { int child_status = 0;
while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {
} }
if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 5)
return 77;
if (!found_red) { if (!found_red) {
if (external_url) { if (external_url) {
fprintf(stderr, "renderer kept the external page uniformly colored: %s\n", url); fprintf(stderr, "renderer kept the external page uniformly colored: %s\n", url);