From f562bc690ddfb021791672bda0a1f3b796cca81f Mon Sep 17 00:00:00 2001 From: Jarcode Date: Wed, 28 Aug 2019 00:24:59 -0700 Subject: [PATCH] Shared library rework & external async API support --- Makefile | 9 ++-- glava-cli/cli.c | 26 +++++++++++ glava-obs/entry.c | 67 +++++++++++++++++++++++++++++ glava/glava.c | 107 +++++++++++++++++++++++++++------------------- glava/glava.h | 40 +++++++++++++++++ glava/glfw_wcb.c | 2 + glava/glx_wcb.c | 38 +++++++++++----- glava/render.c | 89 +++++++++++++++++++++----------------- glava/render.h | 16 +++---- glava/xwin.c | 4 +- meson.build | 35 +++++++++++++-- meson_options.txt | 3 +- 12 files changed, 322 insertions(+), 114 deletions(-) create mode 100644 glava-cli/cli.c create mode 100644 glava-obs/entry.c create mode 100644 glava/glava.h diff --git a/Makefile b/Makefile index bd80b40..d5ddf53 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all install clean +.PHONY: all install clean ninja build # In case these were specified explicitly as options instead of environment variables, export them to child processes export DESTDIR @@ -6,12 +6,12 @@ export CFLAGS BUILD_DIR = build -MESON_CONF = $(BUILD_DIR) +MESON_CONF = $(BUILD_DIR) -Ddisable_obs=true # Support assigning standalone/debug builds as the old Makefile did, otherwise complain ifneq ($(BUILD),debug) - MESON_CONF += --buildtype=release + MESON_CONF += --prefix /usr --buildtype=release ifdef BUILD @echo "WARNING: ignoring build option '$(BUILD)' in compatibility Makefile" endif @@ -34,6 +34,7 @@ $(shell if [ '$(STATE)' != "`cat build_state`" ]; then echo '$(STATE)' > build_s ifndef BUILD @echo "" @echo "PACKAGE MAINTAINER NOTICE: Configuring release build for compatibility with old makefile." + @echo " Some new features may be missing." @echo " If you are a package maintainer consider using meson directly!" @echo "" endif @@ -50,7 +51,7 @@ ninja: build ninja -C $(BUILD_DIR) install: - cd $(BUILD_DIR) && meson install + ninja -C build install clean: rm -rf $(BUILD_DIR) diff --git a/glava-cli/cli.c b/glava-cli/cli.c new file mode 100644 index 0000000..f80d038 --- /dev/null +++ b/glava-cli/cli.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +static renderer_handle handle; + +static void handle_term (int _) { + printf("Interrupt recieved, closing...\n"); + glava_terminate(&handle); +} +static void handle_reload(int _) { + printf("User signal recieved, reloading...\n"); + glava_reload(&handle); +} + +int main(int argc, char** argv) { + const struct sigaction term_action = { .sa_handler = handle_term }; + const struct sigaction reload_action = { .sa_handler = handle_reload }; + sigaction(SIGTERM, &term_action, NULL); + sigaction(SIGINT, &term_action, NULL); + sigaction(SIGUSR1, &reload_action, NULL); + + glava_entry(argc, argv, &handle); + return EXIT_SUCCESS; +} diff --git a/glava-obs/entry.c b/glava-obs/entry.c new file mode 100644 index 0000000..7b7468e --- /dev/null +++ b/glava-obs/entry.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +OBS_DECLARE_MODULE(); + +struct mod_state { + obs_source_t* source; + os_event_t* stop_signal; + pthread_t thread; + bool initialized; +}; + +static const char* get_name(void* _) { + UNUSED_PARAMETER(_); + return "GLava Source"; +} + +static void destroy(void* data) { + struct mod_state* s = (struct mod_state*) data; + if (s) { + if (s->initialized) { + os_event_signal(s->stop_signal); + pthread_join(s->thread, NULL); + } + os_event_destroy(s->stop_signal); + bfree(s); + } +} + +static void* work_thread(void* data) { + return NULL; +} + +static void* create(obs_data_t* settings, obs_source_t* source) { + struct mod_state* s = bzalloc(sizeof(struct mod_state)); + s->source = source; + + if (os_event_init(&s->stop_signal, OS_EVENT_TYPE_MANUAL) != 0) { + destroy(s); + return NULL; + } + + if (pthread_create(&s->thread, NULL, work_thread, s) != 0) { + destroy(s); + return NULL; + } + + s->initialized = true; + return s; +} + +struct obs_source_info glava_src = { + .id = "glava", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_ASYNC_VIDEO, + .get_name = get_name, + .create = create, + .destroy = destroy +}; + +bool obs_module_load(void) { + obs_register_source(&glava_src); + return true; +} diff --git a/glava/glava.c b/glava/glava.c index 3e0ebb3..b302c28 100644 --- a/glava/glava.c +++ b/glava/glava.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -73,7 +72,12 @@ #define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO) /* 0777 */ #endif -static bool reload = false; +static volatile bool reload = false; + +__attribute__((noreturn, visibility("default"))) void glava_return_builtin(void) { exit(EXIT_SUCCESS); } +__attribute__((noreturn, visibility("default"))) void glava_abort_builtin (void) { exit(EXIT_FAILURE); } +__attribute__((noreturn, visibility("default"))) void (*glava_return) (void) = glava_return_builtin; +__attribute__((noreturn, visibility("default"))) void (*glava_abort) (void) = glava_abort_builtin; /* Copy installed shaders/configuration from the installed location (usually /etc/xdg). Modules (folders) will be linked instead of @@ -86,13 +90,13 @@ static void copy_cfg(const char* path, const char* dest, bool verbose) { DIR* dir = opendir(path); if (!dir) { fprintf(stderr, "'%s' does not exist\n", path); - exit(EXIT_FAILURE); + glava_abort(); } umask(~(S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH)); if (mkdir(dest, ACCESSPERMS) && errno != EEXIST) { fprintf(stderr, "could not create directory '%s': %s\n", dest, strerror(errno)); - exit(EXIT_FAILURE); + glava_abort(); } struct dirent* d; @@ -232,28 +236,48 @@ static struct option p_opts[] = { static renderer* rd = NULL; -static void handle_term(int signum) { - if (rd->alive) { - puts("\nInterrupt recieved, closing..."); - rd->alive = false; - } -} - -static void handle_reload(int signum) { - if (rd->alive) { - puts("\nSIGUSR1 recieved, reloading..."); - rd->alive = false; - } - reload = true; -} - #define append_buf(buf, sz_store, ...) \ ({ \ buf = realloc(buf, ++(*sz_store) * sizeof(*buf)); \ buf[*sz_store - 1] = __VA_ARGS__; \ }) -int main(int argc, char** argv) { +/* Wait for renderer target texture to be initialized and valid */ +__attribute__((visibility("default"))) void glava_wait(renderer_handle* ref) { + while(__atomic_load_n(ref, __ATOMIC_SEQ_CST) == NULL) { + /* Edge case: handle has not been assigned */ + struct timespec tv = { + .tv_sec = 0, .tv_nsec = 10 * 1000000 + }; + nanosleep(&tv, NULL); + } + pthread_mutex_lock(&(*ref)->lock); + pthread_cond_wait(&(*ref)->cond, &(*ref)->lock); + pthread_mutex_unlock(&(*ref)->lock); +} + +/* Atomic size request */ +__attribute__((visibility("default"))) void glava_sizereq(renderer_handle r, int x, int y, int w, int h) { + r->sizereq = (typeof(r->sizereq)) { .x = x, .y = y, .w = w, .h = h }; + __atomic_store_n(&r->sizereq_flag, GLAVA_REQ_RESIZE, __ATOMIC_SEQ_CST); +} + +/* Atomic terminate request */ +__attribute__((visibility("default"))) void glava_terminate(renderer_handle* ref) { + renderer_handle store = __atomic_exchange_n(ref, NULL, __ATOMIC_SEQ_CST); + __atomic_store_n(&store->alive, false, __ATOMIC_SEQ_CST); +} + +/* Atomic reload request */ +__attribute__((visibility("default"))) void glava_reload(renderer_handle* ref) { + renderer_handle store = __atomic_exchange_n(ref, NULL, __ATOMIC_SEQ_CST); + __atomic_store_n(&reload, true, __ATOMIC_SEQ_CST); + __atomic_store_n(&store->alive, false, __ATOMIC_SEQ_CST); +} + + +/* Main entry */ +__attribute__((visibility("default"))) void glava_entry(int argc, char** argv, renderer_handle* ret) { /* Evaluate these macros only once, since they allocate */ const char @@ -284,10 +308,10 @@ int main(int argc, char** argv) { case 'm': force = optarg; break; case 'b': backend = optarg; break; case 'a': audio_impl_name = optarg; break; - case '?': exit(EXIT_FAILURE); break; + case '?': glava_abort(); break; case 'V': puts(GLAVA_VERSION_STRING); - exit(EXIT_SUCCESS); + glava_return(); break; default: case 'h': { @@ -297,7 +321,7 @@ int main(int argc, char** argv) { bsz += snprintf(buf + bsz, sizeof(buf) - bsz, "\t\"%s\"%s\n", audio_impls[t]->name, !strcmp(audio_impls[t]->name, audio_impl_name) ? " (default)" : ""); printf(help_str, argc > 0 ? argv[0] : "glava", buf); - exit(EXIT_SUCCESS); + glava_return(); break; } case 'p': { @@ -323,7 +347,7 @@ int main(int argc, char** argv) { if (*parsed_name == '\0') { fprintf(stderr, "Error: invalid pipe binding name: \"%s\"\n" "Zero length names are not permitted.\n", parsed_name); - exit(EXIT_FAILURE); + glava_abort(); } for (char* c = parsed_name; *c != '\0'; ++c) { switch (*c) { @@ -331,7 +355,7 @@ int main(int argc, char** argv) { if (c == parsed_name) { fprintf(stderr, "Error: invalid pipe binding name: \"%s\" ('%c')\n" "Valid names may not start with a number.\n", parsed_name, *c); - exit(EXIT_FAILURE); + glava_abort(); } case 'a' ... 'z': case 'A' ... 'Z': @@ -340,13 +364,13 @@ int main(int argc, char** argv) { fprintf(stderr, "Error: invalid pipe binding name: \"%s\" ('%c')\n" "Valid names may only contain [a..z], [A..Z], [0..9] " "and '_' characters.\n", parsed_name, *c); - exit(EXIT_FAILURE); + glava_abort(); } } for (size_t t = 0; t < binds_sz; ++t) { if (!strcmp(binds[t].name, parsed_name)) { fprintf(stderr, "Error: attempted to re-bind pipe argument: \"%s\"\n", parsed_name); - exit(EXIT_FAILURE); + glava_abort(); } } int type = -1; @@ -364,7 +388,7 @@ int main(int argc, char** argv) { } if (type == -1) { fprintf(stderr, "Error: Unsupported `--pipe` GLSL type: \"%s\"\n", parsed_type); - exit(EXIT_FAILURE); + glava_abort(); } struct rd_bind bd = { .name = parsed_name, @@ -391,13 +415,13 @@ int main(int argc, char** argv) { } if (stdin_type == -1) { fprintf(stderr, "Error: Unsupported `--stdin` GLSL type: \"%s\"\n", optarg); - exit(EXIT_FAILURE); + glava_abort(); } break; } conflict_error: fprintf(stderr, "Error: cannot use `--pipe` and `--stdin` together\n"); - exit(EXIT_FAILURE); + glava_abort(); #ifdef GLAVA_DEBUG case 'T': { entry = "test_rc.glsl"; @@ -409,7 +433,7 @@ int main(int argc, char** argv) { if (copy_mode) { copy_cfg(install_path, user_path, verbose); - exit(EXIT_SUCCESS); + glava_return(); } /* Handle `--force` argument as a request override */ @@ -424,12 +448,6 @@ int main(int argc, char** argv) { append_buf(requests, &requests_sz, NULL); append_buf(binds, &binds_sz, (struct rd_bind) { .name = NULL }); - const struct sigaction term_action = { .sa_handler = handle_term }; - const struct sigaction reload_action = { .sa_handler = handle_reload }; - sigaction(SIGTERM, &term_action, NULL); - sigaction(SIGINT, &term_action, NULL); - sigaction(SIGUSR1, &reload_action, NULL); - float* b0, * b1, * lb, * rb; size_t t; struct audio_data audio; @@ -446,13 +464,14 @@ int main(int argc, char** argv) { if (!impl) { fprintf(stderr, "The specified audio backend (\"%s\") is not available.\n", audio_impl_name); - exit(EXIT_FAILURE); + glava_abort(); } -instantiate: - reload = false; - rd = rd_new(system_shader_paths, entry, (const char**) requests, +instantiate: {} + rd = rd_new(system_shader_paths, entry, (const char**) requests, backend, binds, stdin_type, desktop, verbose, test); + if (ret) + __atomic_store_n(ret, rd, __ATOMIC_SEQ_CST); b0 = malloc(rd->bufsize_request * sizeof(float)); b1 = malloc(rd->bufsize_request * sizeof(float)); @@ -488,7 +507,7 @@ instantiate: if (verbose) printf("Using audio source: %s\n", audio.source); pthread_create(&thread, NULL, impl->entry, (void*) &audio); - while (rd->alive) { + while (__atomic_load_n(&rd->alive, __ATOMIC_SEQ_CST)) { rd_time(rd); /* update timer for this frame */ @@ -526,7 +545,7 @@ instantiate: if (rd_test_evaluate(rd)) { fprintf(stderr, "Test results did not match expected output\n"); fflush(stderr); - exit(EXIT_FAILURE); + glava_abort(); } } #endif @@ -542,6 +561,6 @@ instantiate: free(lb); free(rb); rd_destroy(rd); - if (reload) + if (__atomic_exchange_n(&reload, false, __ATOMIC_SEQ_CST)) goto instantiate; } diff --git a/glava/glava.h b/glava/glava.h new file mode 100644 index 0000000..9ab6b05 --- /dev/null +++ b/glava/glava.h @@ -0,0 +1,40 @@ +#ifndef _GLAVA_H +#define _GLAVA_H + +#include +#include +#include + +#define GLAVA_REQ_NONE 0 +#define GLAVA_REQ_RESIZE 1 + +struct gl_data; + +typedef struct renderer { + volatile bool alive; + bool mirror_input; + size_t bufsize_request, rate_request, samplesize_request; + char* audio_source_request; + int off_tex; /* final GL texture for offscreen rendering */ + pthread_mutex_t lock; /* lock for reading from offscreen texture */ + pthread_cond_t cond; /* cond for reading from offscreen texture */ + volatile struct { + int x, y, w, h; + } sizereq; + volatile int sizereq_flag; + struct gl_data* gl; +} renderer; + +/* External API */ + +typedef struct renderer* volatile renderer_handle; +__attribute__((noreturn, visibility("default"))) void (*glava_abort) (void); +__attribute__((noreturn, visibility("default"))) void (*glava_return) (void); +__attribute__((visibility("default"))) void glava_assign_external_ctx (void* ctx); +__attribute__((visibility("default"))) void glava_entry (int argc, char** argv, renderer_handle* ret); +__attribute__((visibility("default"))) void glava_terminate (renderer_handle* ref); +__attribute__((visibility("default"))) void glava_reload (renderer_handle* ref); +__attribute__((visibility("default"))) void glava_sizereq (renderer_handle r, int x, int y, int w, int h); +__attribute__((visibility("default"))) void glava_wait (renderer_handle* ref); + +#endif /* _GLAVA_H */ diff --git a/glava/glfw_wcb.c b/glava/glfw_wcb.c index a7e98fd..791bfe1 100644 --- a/glava/glfw_wcb.c +++ b/glava/glfw_wcb.c @@ -65,6 +65,8 @@ DECL_WINDOW_HINT_STUB(set_maximized); extern struct gl_wcb wcb_glfw; +static bool offscreen(void) { return false; } + static void* create_and_bind(const char* name, const char* class, const char* type, const char** states, size_t states_sz, diff --git a/glava/glx_wcb.c b/glava/glx_wcb.c index 6fadfbf..f0dc881 100644 --- a/glava/glx_wcb.c +++ b/glava/glx_wcb.c @@ -25,14 +25,14 @@ #include "render.h" #include "xwin.h" -typedef struct __GLXcontextRec *GLXContext; +typedef struct __GLXcontextRec* GLXContext; typedef XID GLXPixmap; typedef XID GLXDrawable; typedef void (*__GLXextFuncPtr)(void); /* GLX 1.3 and later */ -typedef struct __GLXFBConfigRec *GLXFBConfig; +typedef struct __GLXFBConfigRec* GLXFBConfig; typedef XID GLXFBConfigID; typedef XID GLXContextID; typedef XID GLXWindow; @@ -59,7 +59,6 @@ typedef XID GLXPbuffer; #define GLX_ACCUM_BLUE_SIZE 16 #define GLX_ACCUM_ALPHA_SIZE 17 - /* * Error codes returned by glXGetConfig: */ @@ -184,6 +183,20 @@ struct glxwin { static Atom ATOM__MOTIF_WM_HINTS, ATOM_WM_DELETE_WINDOW, ATOM_WM_PROTOCOLS, ATOM__NET_ACTIVE_WINDOW, ATOM__XROOTPMAP_ID; +static GLXContext sharelist_ctx; +static bool sharelist_assigned = false; + +static bool offscreen(void) { + return sharelist_assigned; +} + +/* Public function that can be called before GLava instantiation for offscreen rendering hooks */ +/* This hook resides here since it relies on GLX functionality. */ +__attribute__((visibility("default"))) void glava_assign_external_ctx(void* ctx) { + sharelist_ctx = (GLXContext) ctx; + sharelist_assigned = true; +} + static void init(void) { display = XOpenDisplay(NULL); if (!display) { @@ -201,7 +214,7 @@ static void init(void) { if (!hgl && !hglx) { fprintf(stderr, "Failed to load GLX functions (libGL and libGLX do not exist!)\n"); - exit(EXIT_FAILURE); + glava_abort(); } /* Depending on the graphics driver, the GLX functions that we need may either be in libGL or @@ -212,7 +225,7 @@ static void init(void) { if (!s && hglx) s = dlsym(hglx, symbol); if (!s) { fprintf(stderr, "Failed to resolve GLX symbol: `%s`\n", symbol); - exit(EXIT_FAILURE); + glava_abort(); } return s; } @@ -338,7 +351,12 @@ static void* create_and_bind(const char* name, const char* class, int d, int h, int x, int y, int version_major, int version_minor, - bool clickthrough, bool offscreen) { + bool clickthrough, bool off) { + + /* Assume offscreen rendering if hook has been used */ + if (offscreen()) + off = true; + struct glxwin* w = malloc(sizeof(struct glxwin)); *w = (struct glxwin) { .override_state = '\0', @@ -347,7 +365,7 @@ static void* create_and_bind(const char* name, const char* class, .should_render = true, .bg_changed = false, .clickthrough = false, - .offscreen = offscreen + .offscreen = off }; XVisualInfo* vi; @@ -362,7 +380,7 @@ static void* create_and_bind(const char* name, const char* class, "\nGLX extension version mismatch on the current display (1.4+ required, %d.%d available)\n" "This is usually due to an outdated X server or graphics drivers.\n\n", glx_minor, glx_major); - exit(EXIT_FAILURE); + glava_abort(); } static int gl_attrs[] = { @@ -393,7 +411,7 @@ static void* create_and_bind(const char* name, const char* class, "glXChooseFBConfig(): failed with attrs " "(GLX_CONTEXT_MAJOR_VERSION_ARB, GLX_CONTEXT_MINOR_VERSION_ARB)\n\n", version_major, version_minor); - exit(EXIT_FAILURE); + glava_abort(); } for (int t = 0; t < fb_sz; ++t) { @@ -486,7 +504,7 @@ static void* create_and_bind(const char* name, const char* class, abort(); } - if (!(w->context = glXCreateContextAttribsARB(display, config, 0, True, context_attrs))) { + if (!(w->context = glXCreateContextAttribsARB(display, config, sharelist_assigned ? sharelist_ctx : 0, True, context_attrs))) { fprintf(stderr, "glXCreateContextAttribsARB(): failed\n"); abort(); } diff --git a/glava/render.c b/glava/render.c index 7389c47..b09e48d 100644 --- a/glava/render.c +++ b/glava/render.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -147,24 +148,21 @@ struct gl_data { bool bg_setup; GLuint sm_utex, sm_usz, sm_uw; bool sm_setup; + bool test_mode; + struct gl_sfbo off_sfbo; #ifdef GLAVA_DEBUG struct { float r, g, b, a; } test_eval_color; bool debug_verbose; bool assigned_debug_cb; - bool test_mode; - struct gl_sfbo test_sfbo; #endif }; - -#ifdef GLAVA_DEBUG bool rd_get_test_mode(struct renderer* r) { struct gl_data* gl = r->gl; return gl->test_mode; } -#endif /* load shader file */ static GLuint shaderload(const char* rpath, @@ -819,8 +817,14 @@ struct renderer* rd_new(const char** paths, const char* entry, .bufsize_request = 8192, .rate_request = 22000, .samplesize_request = 1024, - .audio_source_request = NULL + .audio_source_request = NULL, + .off_tex = 0, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + .sizereq_flag = 0 }; + + pthread_mutex_lock(&r->lock); struct gl_data* gl = r->gl; *gl = (struct gl_data) { @@ -863,19 +867,19 @@ struct renderer* rd_new(const char** paths, const char* entry, .binds = bindings, .bg_setup = false, .sm_setup = false, - #ifdef GLAVA_DEBUG - .test_eval_color = { 0.0F, 0.0F, 0.0F, 0.0F }, - .debug_verbose = verbose, - .assigned_debug_cb = false, .test_mode = test_mode, - .test_sfbo = { + .off_sfbo = { .name = "test", .shader = 0, .indirect = false, .nativeonly = false, .binds = NULL, .binds_sz = 0 - } + }, + #ifdef GLAVA_DEBUG + .test_eval_color = { 0.0F, 0.0F, 0.0F, 0.0F }, + .debug_verbose = verbose, + .assigned_debug_cb = false, #endif }; @@ -905,7 +909,7 @@ struct renderer* rd_new(const char** paths, const char* entry, fprintf(stderr, "\t\"%s\"\n", wcbs[t]->name); } } - exit(EXIT_FAILURE); + glava_abort(); } if (verbose) printf("Using backend: '%s'\n", backend); @@ -966,7 +970,7 @@ struct renderer* rd_new(const char** paths, const char* entry, if (!gl->copy_desktop && !native_opacity && strcmp("none", (char*) args[0])) { fprintf(stderr, "Invalid opacity option: '%s'\n", (char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } }) }, @@ -992,7 +996,7 @@ struct renderer* rd_new(const char** paths, const char* entry, }; if (!ext_parse_color((char*) args[0], 2, results)) { fprintf(stderr, "Invalid value for `setbg` request: '%s'\n", (char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } }) }, @@ -1008,7 +1012,7 @@ struct renderer* rd_new(const char** paths, const char* entry, }; if (!ext_parse_color((char*) args[0], 2, results)) { fprintf(stderr, "Invalid value for `setbg` request: '%s'\n", (char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } }) }, @@ -1042,7 +1046,7 @@ struct renderer* rd_new(const char** paths, const char* entry, current->nativeonly = *(bool*) args[0]; else { fprintf(stderr, "`nativeonly` request needs module context\n"); - exit(EXIT_FAILURE); + glava_abort(); } }) }, @@ -1157,7 +1161,7 @@ struct renderer* rd_new(const char** paths, const char* entry, fprintf(stderr, "Cannot add transformation to uniform '%s':" " uniform does not exist! (%d present in this unit)\n", (const char*) args[0], (int) current->binds_sz); - exit(EXIT_FAILURE); + glava_abort(); } struct gl_transform* tran = NULL; for (t = 0; t < sizeof(transform_functions) / sizeof(struct gl_transform); ++t) { @@ -1170,12 +1174,12 @@ struct renderer* rd_new(const char** paths, const char* entry, fprintf(stderr, "Cannot add transformation '%s' to uniform '%s':" " transform function does not exist!\n", (const char*) args[1], (const char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } if (tran->type != bind->type) { fprintf(stderr, "Cannot apply '%s' to uniform '%s': mismatching types\n", (const char*) args[1], (const char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } ++bind->t_sz; bind->transformations = @@ -1190,13 +1194,13 @@ struct renderer* rd_new(const char** paths, const char* entry, if (!current) { fprintf(stderr, "Cannot bind uniform '%s' outside of a context" " (load a module first!)\n", (const char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } struct gl_bind_src* src = lookup_bind_src((const char*) args[0]); if (!src) { fprintf(stderr, "Cannot bind uniform '%s': bind type does not exist!\n", (const char*) args[0]); - exit(EXIT_FAILURE); + glava_abort(); } ++current->binds_sz; current->binds = realloc(current->binds, current->binds_sz * sizeof(struct gl_bind)); @@ -1246,7 +1250,7 @@ struct renderer* rd_new(const char** paths, const char* entry, errno != ENOTDIR && errno != ELOOP ) { fprintf(stderr, "Failed to load entry '%s': %s\n", se_buf, strerror(errno)); - exit(EXIT_FAILURE); + glava_abort(); } else continue; } fstat(fd, &st); @@ -1275,7 +1279,7 @@ struct renderer* rd_new(const char** paths, const char* entry, errno != ELOOP) { fprintf(stderr, "Failed to load desktop environment specific presets " "at '%s': %s\n", se_buf, strerror(errno)); - exit(EXIT_FAILURE); + glava_abort(); } else { printf("No presets for current desktop environment (\"%s\"), " "using default presets for embedding\n", env); @@ -1284,7 +1288,7 @@ struct renderer* rd_new(const char** paths, const char* entry, if (fd == -1) { fprintf(stderr, "Failed to load default presets at '%s': %s\n", se_buf, strerror(errno)); - exit(EXIT_FAILURE); + glava_abort(); } } } @@ -1336,7 +1340,7 @@ struct renderer* rd_new(const char** paths, const char* entry, "No module was selected, edit '%s' to load " "a module with `#request mod [name]`\n", entry); - exit(EXIT_FAILURE); + glava_abort(); } gl->w = gl->wcb->create_and_bind(wintitle, "GLava", xwintype, (const char**) xwinstates, xwinstates_sz, @@ -1442,7 +1446,7 @@ struct renderer* rd_new(const char** paths, const char* entry, if (skip && verbose) printf("disabled: '%s'\n", d->d_name); /* check for compilation failure */ if (!id && !skip) - exit(EXIT_FAILURE); + glava_abort(); s->shader = id; @@ -1472,7 +1476,7 @@ struct renderer* rd_new(const char** paths, const char* entry, s->pipe_uniforms[u] = glGetUniformLocation(id, buf); } else { fprintf(stderr, "failed to format binding: \"%s\"\n", bd->name); - exit(EXIT_FAILURE); + glava_abort(); } ++u; } @@ -1492,14 +1496,15 @@ struct renderer* rd_new(const char** paths, const char* entry, gl->stages = stages; gl->stages_sz = count; - - #ifdef GLAVA_DEBUG - { + + if (gl->test_mode || gl->wcb->offscreen()) { int w, h; gl->wcb->get_fbsize(gl->w, &w, &h); - setup_sfbo(&gl->test_sfbo, w, h); + setup_sfbo(&gl->off_sfbo, w, h); + r->off_tex = gl->off_sfbo.tex; + pthread_cond_signal(&r->cond); + pthread_mutex_unlock(&r->lock); } - #endif { struct gl_sfbo* final = NULL; @@ -1636,6 +1641,13 @@ bool rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi irb[t] = irbs + ((irbe - irbs) * mod); } } + + /* Handle external resize requests */ + if (gl->wcb->offscreen()) { + if (__atomic_exchange_n(&r->sizereq_flag, GLAVA_REQ_NONE, __ATOMIC_SEQ_CST) == GLAVA_REQ_RESIZE) + gl->wcb->set_geometry(gl->w, r->sizereq.x, r->sizereq.y, r->sizereq.w, r->sizereq.h); + } + int ww, wh, wx, wy; gl->wcb->get_fbsize(gl->w, &ww, &wh); gl->wcb->get_pos(gl->w, &wx, &wy); @@ -1647,9 +1659,8 @@ bool rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi setup_sfbo(&gl->stages[t], ww, wh); } } - #ifdef GLAVA_DEBUG - setup_sfbo(&gl->test_sfbo, ww, wh); - #endif + if (gl->test_mode || gl->wcb->offscreen()) + setup_sfbo(&gl->off_sfbo, ww, wh); } /* Resize and grab new background data if needed */ @@ -1843,11 +1854,9 @@ bool rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi if (current->indirect) glBindFramebuffer(GL_FRAMEBUFFER, current->fbo); - #ifdef GLAVA_DEBUG - if (!current->indirect && gl->test_mode) { - glBindFramebuffer(GL_FRAMEBUFFER, gl->test_sfbo.fbo); + if (!current->indirect && (gl->test_mode || gl->wcb->offscreen())) { + glBindFramebuffer(GL_FRAMEBUFFER, gl->off_sfbo.fbo); } - #endif glClear(GL_COLOR_BUFFER_BIT); diff --git a/glava/render.h b/glava/render.h index b23724f..242edaa 100644 --- a/glava/render.h +++ b/glava/render.h @@ -2,6 +2,11 @@ #ifndef RENDER_H #define RENDER_H +#include +#include +#include +#include "glava.h" + extern const struct { const char* n; int i; @@ -18,15 +23,6 @@ extern bool glad_instantiated; #define PIPE_DEFAULT "_" -struct gl_data; - -typedef struct renderer { - bool alive, mirror_input; - size_t bufsize_request, rate_request, samplesize_request; - char* audio_source_request; - struct gl_data* gl; -} renderer; - struct rd_bind { const char* name; const char* stype; @@ -53,6 +49,7 @@ struct gl_wcb* rd_get_wcb (struct renderer*); /* gl_wcb - OpenGL Window Creation Backend interface */ struct gl_wcb { const char* name; + bool (*offscreen) (void); void (*init) (void); void* (*create_and_bind)(const char* name, const char* class, const char* type, const char** states, @@ -96,6 +93,7 @@ struct gl_wcb { #define WCB_ATTACH(B, N) \ struct gl_wcb N = { \ .name = B, \ + WCB_FUNC(offscreen), \ WCB_FUNC(init), \ WCB_FUNC(create_and_bind), \ WCB_FUNC(should_close), \ diff --git a/glava/xwin.c b/glava/xwin.c index 8058476..5332a43 100644 --- a/glava/xwin.c +++ b/glava/xwin.c @@ -295,7 +295,7 @@ unsigned int xwin_copyglbg(struct renderer* rd, unsigned int tex) { if ((shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0777)) == -1) { fprintf(stderr, "shmget() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); + glava_abort(); } shminfo.shmaddr = image->data = shmat(shminfo.shmid, 0, 0); shminfo.readOnly = false; @@ -314,7 +314,7 @@ unsigned int xwin_copyglbg(struct renderer* rd, unsigned int tex) { if (image) { bool invalid = false, aligned = false; - GLenum type; + GLenum type = 0; switch (image->bits_per_pixel) { case 16: switch (image->depth) { diff --git a/meson.build b/meson.build index e8097f2..47e7e3e 100644 --- a/meson.build +++ b/meson.build @@ -21,7 +21,6 @@ else add_project_arguments( '-O2', '-Wstringop-overflow=0', - '-Wmaybe-uninitialized=0', language: 'c' ) if get_option('glad') @@ -83,17 +82,27 @@ endif add_project_arguments( '-DGLAVA_VERSION="'+glava_version+'"', - '-DSHADER_INSTALL_PATH="'+shaderdir+'"', + '-DSHADER_INSTALL_PATH="'+shaderdir+'/glava"', + '-I/usr/include/obs', + '-fvisibility=hidden', language: 'c' ) -executable( +libglava = shared_library( 'glava', sources: run_command('find', 'glava', '-type', 'f', '-name', '*.c', '-print').stdout().strip().split('\n'), dependencies: glava_dependencies, install: true ) +executable( + 'glava', + sources: run_command('find', 'glava-cli', '-type', 'f', '-name', '*.c', '-print').stdout().strip().split('\n'), + link_with: libglava, + include_directories: 'glava', + install: true +) + executable( 'glava-config', sources: run_command('find', 'glava-config', '-type', 'f', '-name', '*.c', '-print').stdout().strip().split('\n'), @@ -105,4 +114,22 @@ executable( install: true ) -install_subdir('shaders/glava', install_dir: shaderdir+'/../') +if not get_option('disable_obs') + shared_library( + 'glava-obs', + sources: run_command('find', 'glava-obs', '-type', 'f', '-name', '*.c', '-print').stdout().strip().split('\n'), + link_with: libglava, + dependencies: [ + dependency('threads'), + cc.find_library('GL'), + cc.find_library('X11'), + cc.find_library('obs'), + cc.find_library('dl') + ], + install: true, + install_dir : '/usr/lib/obs-plugins' + ) +endif + +install_subdir('shaders/glava', install_dir: shaderdir) +install_headers('glava/glava.h') diff --git a/meson_options.txt b/meson_options.txt index a6461a8..efe68ed 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,5 +1,6 @@ -option('shaderdir', type: 'string', value: '/etc/xdg/glava/', description: 'Default shader directory') +option('shaderdir', type: 'string', value: '/etc/xdg', description: 'System shader directory location') option('enable_glfw', type: 'boolean', value: false, description: 'Enable GLFW backend') option('disable_glx', type: 'boolean', value: false, description: 'Disable GLX') option('standalone', type: 'boolean', value: false, description: 'OS-independent build') option('glad', type: 'boolean', value: false, description: 'Regenerate GLAD') +option('disable_obs', type: 'boolean', value: false, description: 'Dsiable OBS plugin support')