181 lines
6.4 KiB
C
181 lines
6.4 KiB
C
#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);
|
|
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
|
|
? 0
|
|
: 1;
|
|
}
|