gpu accelerated

This commit is contained in:
2026-07-18 08:18:50 +05:00
commit 2bbee73425
14 changed files with 1975 additions and 0 deletions

70
tests/module-load-test.c Normal file
View File

@@ -0,0 +1,70 @@
#include <obs/obs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "usage: %s plugin.so data-directory renderer\n", argv[0]);
return 2;
}
char temporary[] = "/tmp/obs-webkit-module-test-XXXXXX";
if (!mkdtemp(temporary)) {
perror("mkdtemp");
return 2;
}
char renderer_link[512];
char locale_link[512];
char locale_source[512];
snprintf(renderer_link, sizeof(renderer_link), "%s/obs-webkit-renderer", temporary);
snprintf(locale_link, sizeof(locale_link), "%s/locale", temporary);
snprintf(locale_source, sizeof(locale_source), "%s/locale", argv[2]);
if (symlink(argv[3], renderer_link) || symlink(locale_source, locale_link)) {
perror("symlink");
return 2;
}
if (!obs_startup("en-US", NULL, NULL)) {
fprintf(stderr, "obs_startup failed\n");
return 1;
}
obs_module_t *module = NULL;
int result = obs_open_module(&module, argv[1], temporary);
if (result != MODULE_SUCCESS || !obs_init_module(module)) {
fprintf(stderr, "module load failed: %d\n", result);
obs_shutdown();
return 1;
}
const char *name = obs_source_get_display_name("webkit_browser_source");
uint32_t flags = obs_get_source_output_flags("webkit_browser_source");
bool valid = name && strstr(name, "WebKit") && (flags & OBS_SOURCE_ASYNC_VIDEO) &&
(flags & OBS_SOURCE_INTERACTION);
if (!valid)
fprintf(stderr, "source registration is incomplete (name=%s, flags=0x%x)\n",
name ? name : "(null)", flags);
obs_data_t *settings = obs_data_create();
obs_data_set_string(settings, "url", "data:text/html,<body style='background:red'></body>");
obs_data_set_int(settings, "width", 64);
obs_data_set_int(settings, "height", 64);
obs_data_set_int(settings, "fps", 10);
obs_data_set_bool(settings, "transparent", true);
obs_data_set_bool(settings, "hardware_acceleration", false);
obs_source_t *source = obs_source_create_private("webkit_browser_source", "smoke", settings);
obs_data_release(settings);
if (!source || obs_source_get_width(source) != 64 || obs_source_get_height(source) != 64) {
fprintf(stderr, "source lifecycle setup failed\n");
valid = false;
}
if (source) {
usleep(500000);
obs_source_release(source);
}
obs_shutdown();
unlink(renderer_link);
unlink(locale_link);
rmdir(temporary);
return valid ? 0 : 1;
}

14
tests/protocol-test.c Normal file
View File

@@ -0,0 +1,14 @@
#include "protocol.h"
#include <assert.h>
#include <stddef.h>
int main(void)
{
assert(sizeof(struct owb_config_header) == 36);
assert(sizeof(struct owb_command) == 100);
assert(sizeof(struct owb_shared_frames) == 40);
assert(offsetof(struct owb_command, text) == 36);
assert(OWB_CONFIG_MAGIC != OWB_COMMAND_MAGIC);
return 0;
}

177
tests/renderer-fps-test.c Normal file
View File

@@ -0,0 +1,177 @@
#include "protocol.h"
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
extern char **environ;
static bool transfer(int fd, void *data, size_t size, bool writing)
{
uint8_t *cursor = data;
while (size) {
if (!writing) {
struct pollfd item = {.fd = fd, .events = POLLIN};
if (poll(&item, 1, 5000) <= 0)
return false;
}
ssize_t count = writing ? write(fd, cursor, size) : read(fd, cursor, size);
if (count < 0 && errno == EINTR)
continue;
if (count <= 0)
return false;
cursor += (size_t)count;
size -= (size_t)count;
}
return true;
}
static double seconds_since(const struct timespec *start)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
int main(int argc, char **argv)
{
if (argc != 2 && argc != 5 && argc != 6)
return 2;
int control[2], frames[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, control) || pipe(frames))
return 2;
const uint32_t width = argc > 2 ? (uint32_t)strtoul(argv[2], NULL, 10) : 1280;
const uint32_t height = argc > 3 ? (uint32_t)strtoul(argv[3], NULL, 10) : 720;
const uint32_t requested_fps = argc > 4 ? (uint32_t)strtoul(argv[4], NULL, 10) : 30;
size_t shared_size = owb_shared_size(width, height);
int shared_fd = memfd_create("renderer-fps-frames", MFD_CLOEXEC);
if (shared_fd < 0 || ftruncate(shared_fd, (off_t)shared_size) != 0)
return 2;
struct owb_shared_frames *shared = mmap(NULL, shared_size, PROT_READ | PROT_WRITE,
MAP_SHARED, shared_fd, 0);
if (shared == MAP_FAILED)
return 2;
memset(shared, 0, sizeof(*shared));
shared->magic = OWB_SHARED_MAGIC;
shared->version = OWB_PROTOCOL_VERSION;
shared->width = width;
shared->height = height;
shared->stride = width * 4;
shared->slots = OWB_SHARED_FRAME_SLOTS;
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
posix_spawn_file_actions_adddup2(&actions, control[1], STDIN_FILENO);
posix_spawn_file_actions_adddup2(&actions, frames[1], STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&actions, shared_fd, OWB_SHARED_FRAME_FD);
char *child_argv[] = {argv[1], NULL};
pid_t child = 0;
int error = posix_spawn(&child, argv[1], &actions, NULL, child_argv, environ);
posix_spawn_file_actions_destroy(&actions);
close(control[1]);
close(frames[1]);
if (error)
return 2;
const char url[] = "data:text/html,<div id='card'></div>";
const char css_blur[] =
"html,body{margin:0;background:transparent;overflow:hidden}"
"body:after{content:'';position:fixed;left:0;top:0;width:32px;height:32px;"
"animation:probe .73s linear infinite alternate}"
"#card{position:absolute;left:15%;top:20%;width:45%;height:55%;"
"background:rgba(30,150,255,.75);will-change:transform,filter;"
"animation:fx 1s ease-in-out infinite alternate}"
"@keyframes fx{from{transform:translateX(0) scale(.92);filter:blur(2px)}"
"to{transform:translateX(45%) scale(1.08);filter:blur(18px)}}"
"@keyframes probe{from{background:#000}to{background:#fff}}";
const char css_plain[] =
"html,body{margin:0;background:transparent;overflow:hidden}"
"body:after{content:'';position:fixed;left:0;top:0;width:32px;height:32px;"
"animation:probe .73s linear infinite alternate}"
"#card{position:absolute;left:15%;top:20%;width:45%;height:55%;"
"background:rgba(30,150,255,.75);animation:fx 1s ease-in-out infinite alternate}"
"@keyframes fx{from{transform:translateX(0) scale(.92)}"
"to{transform:translateX(45%) scale(1.08)}}"
"@keyframes probe{from{background:#000}to{background:#fff}}";
const char *css = argc == 6 && strstr(argv[5], "plain") != NULL ? css_plain : css_blur;
size_t css_length = strlen(css);
struct owb_config_header config = {
.magic = OWB_CONFIG_MAGIC, .version = OWB_PROTOCOL_VERSION,
.width = width, .height = height, .fps = requested_fps, .transparent = 1,
.hardware_acceleration = 1,
.url_length = sizeof(url) - 1, .css_length = (uint32_t)css_length,
};
bool okay = transfer(control[0], &config, sizeof(config), true) &&
transfer(control[0], (void *)url, sizeof(url) - 1, true) &&
transfer(control[0], (void *)css, css_length, true);
size_t frame_size = (size_t)width * height * 4;
uint8_t *pixels = malloc(frame_size);
unsigned frames_read = 0;
unsigned changed_frames = 0;
uint64_t previous_hash = 0;
struct timespec start = {0};
while (okay) {
uint8_t slot;
if (!transfer(frames[0], &slot, sizeof(slot), false) || slot >= OWB_SHARED_FRAME_SLOTS)
break;
bool stable = false;
for (unsigned attempt = 0; attempt < 3 && !stable; attempt++) {
uint64_t before = __atomic_load_n(&shared->sequence[slot], __ATOMIC_ACQUIRE);
if (before & 1u)
continue;
memcpy(pixels, owb_shared_slot(shared, slot), frame_size);
uint64_t after = __atomic_load_n(&shared->sequence[slot], __ATOMIC_ACQUIRE);
stable = before == after && !(after & 1u);
}
if (!stable)
continue;
uint64_t hash = 1469598103934665603ull;
for (uint32_t y = 0; y < height; y += 16) {
for (uint32_t x = 0; x < width; x += 16) {
size_t offset = ((size_t)y * width + x) * 4;
uint32_t sample;
memcpy(&sample, pixels + offset, sizeof(sample));
hash ^= sample;
hash *= 1099511628211ull;
}
}
if (!frames_read || hash != previous_hash)
changed_frames++;
previous_hash = hash;
if (!frames_read)
clock_gettime(CLOCK_MONOTONIC, &start);
frames_read++;
if (seconds_since(&start) >= 4.0)
break;
}
double elapsed = frames_read ? seconds_since(&start) : 0.0;
double fps = elapsed > 0.0 ? (double)(frames_read - 1) / elapsed : 0.0;
double changed_fps = elapsed > 0.0 ? (double)(changed_frames - 1) / elapsed : 0.0;
fprintf(stderr,
"blur-transition: %u frames in %.3f s = %.2f FPS; %u changed = %.2f FPS "
"(requested %u)\n",
frames_read, elapsed, fps, changed_frames, changed_fps, requested_fps);
free(pixels);
munmap(shared, shared_size);
close(shared_fd);
shutdown(control[0], SHUT_RDWR);
close(control[0]);
close(frames[0]);
kill(child, SIGTERM);
while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
}
return frames_read && fps >= requested_fps * 0.80 && changed_fps >= requested_fps * 0.50
? 0
: 1;
}

192
tests/renderer-smoke-test.c Normal file
View File

@@ -0,0 +1,192 @@
#include "protocol.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
extern char **environ;
static bool write_exact(int fd, const void *data, size_t size)
{
const uint8_t *cursor = data;
while (size) {
ssize_t count = write(fd, cursor, size);
if (count < 0 && errno == EINTR)
continue;
if (count <= 0)
return false;
cursor += (size_t)count;
size -= (size_t)count;
}
return true;
}
static bool read_frame(int fd, uint8_t *pixels, size_t size, int timeout_ms)
{
size_t used = 0;
while (used < size) {
struct pollfd pollfd = {.fd = fd, .events = POLLIN};
int ready = poll(&pollfd, 1, timeout_ms);
if (ready <= 0)
return false;
ssize_t count = read(fd, pixels + used, size - used);
if (count < 0 && errno == EINTR)
continue;
if (count <= 0)
return false;
used += (size_t)count;
}
return true;
}
int main(int argc, char **argv)
{
if (argc < 2 || argc > 4) {
fprintf(stderr,
"usage: %s /path/to/obs-webkit-renderer [opaque | opaque-url URL]\n",
argv[0]);
return 2;
}
bool external_url = argc == 4 && strcmp(argv[2], "opaque-url") == 0;
bool opaque = external_url || (argc == 3 && strcmp(argv[2], "opaque") == 0);
int control[2];
int frames[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, control) || pipe(frames)) {
perror("IPC");
return 2;
}
const uint32_t width = external_url ? 1890 : 64;
const uint32_t height = external_url ? 660 : 64;
size_t shared_size = owb_shared_size(width, height);
int shared_fd = memfd_create("renderer-smoke-frames", MFD_CLOEXEC);
if (shared_fd < 0 || ftruncate(shared_fd, (off_t)shared_size) != 0)
return 2;
struct owb_shared_frames *shared = mmap(NULL, shared_size, PROT_READ | PROT_WRITE,
MAP_SHARED, shared_fd, 0);
if (shared == MAP_FAILED)
return 2;
memset(shared, 0, sizeof(*shared));
shared->magic = OWB_SHARED_MAGIC;
shared->version = OWB_PROTOCOL_VERSION;
shared->width = width;
shared->height = height;
shared->stride = width * 4;
shared->slots = OWB_SHARED_FRAME_SLOTS;
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
posix_spawn_file_actions_adddup2(&actions, control[1], STDIN_FILENO);
posix_spawn_file_actions_adddup2(&actions, frames[1], STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&actions, shared_fd, OWB_SHARED_FRAME_FD);
char *child_argv[] = {argv[1], NULL};
pid_t child = 0;
int error = posix_spawn(&child, argv[1], &actions, NULL, child_argv, environ);
posix_spawn_file_actions_destroy(&actions);
close(control[1]);
close(frames[1]);
if (error) {
fprintf(stderr, "posix_spawn: %s\n", strerror(error));
return 2;
}
const char internal_url[] = "data:text/html,<body><div id='box'></div></body>";
const char transparent_css[] = "html,body{margin:0;background:transparent!important}"
"#box{position:fixed;inset:0;background:rgba(255,0,0,.5)}";
const char opaque_css[] = "html,body{margin:0;background:rgb(7,19,31)!important}"
"#box{position:fixed;left:16px;top:16px;width:32px;height:32px;"
"background:rgb(241,23,17);transform:translateZ(0);filter:blur(.1px)}";
const char *url = external_url ? argv[3] : internal_url;
const char *css = external_url ? "" : opaque ? opaque_css : transparent_css;
struct owb_config_header config = {
.magic = OWB_CONFIG_MAGIC,
.version = OWB_PROTOCOL_VERSION,
.width = width,
.height = height,
.fps = 10,
.transparent = opaque ? 0 : 1,
/* GPU rendering must still use the private Xvfb and produce a frame. */
.hardware_acceleration = 1,
.url_length = (uint32_t)strlen(url),
.css_length = (uint32_t)strlen(css),
};
bool sent = write_exact(control[0], &config, sizeof(config)) &&
write_exact(control[0], url, strlen(url)) &&
write_exact(control[0], css, strlen(css));
uint8_t last_bgra[4] = {0};
bool found_red = false;
int max_attempts = external_url ? 120 : 60;
for (int attempt = 0; sent && attempt < max_attempts; attempt++) {
uint8_t slot;
if (!read_frame(frames[0], &slot, sizeof(slot), 1000))
continue;
if (slot >= OWB_SHARED_FRAME_SLOTS)
continue;
uint8_t *frame = owb_shared_slot(shared, slot);
if (external_url) {
bool nonuniform = false;
uint32_t first;
memcpy(&first, frame, sizeof(first));
for (uint32_t y = 0; y < height && !nonuniform; y += 8) {
for (uint32_t x = 0; x < width; x += 8) {
uint32_t sample;
memcpy(&sample, frame + ((size_t)y * width + x) * 4,
sizeof(sample));
if (sample != first) {
nonuniform = true;
break;
}
}
}
found_red = nonuniform;
if ((attempt + 1) % 10 == 0)
fprintf(stderr, "external page at %.1fs: %s\n",
(double)(attempt + 1) / 10.0,
nonuniform ? "content" : "uniform");
continue;
}
size_t center = ((size_t)(height / 2) * width + width / 2) * 4;
memcpy(last_bgra, frame + center, sizeof(last_bgra));
bool expected = opaque
? last_bgra[0] < 32 && last_bgra[1] < 40 && last_bgra[2] > 210 &&
last_bgra[3] == 255
: last_bgra[0] < 8 && last_bgra[1] < 8 && last_bgra[2] > 120 &&
last_bgra[2] < 136 && last_bgra[3] > 120 && last_bgra[3] < 136;
if (expected) {
found_red = true;
break;
}
}
shutdown(control[0], SHUT_RDWR);
close(control[0]);
close(frames[0]);
munmap(shared, shared_size);
close(shared_fd);
kill(child, SIGTERM);
while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
}
if (!found_red) {
if (external_url) {
fprintf(stderr, "renderer kept the external page uniformly colored: %s\n", url);
return 1;
}
fprintf(stderr,
"renderer did not produce the expected %s red BGRA frame "
"(center BGRA=%u,%u,%u,%u)\n",
opaque ? "opaque" : "transparent", last_bgra[0], last_bgra[1],
last_bgra[2], last_bgra[3]);
return 1;
}
return 0;
}