glava-obs functionality, see #129

This commit is contained in:
Jarcode
2019-08-28 20:18:47 -07:00
parent 5de224c3e1
commit c9c134a3e2
7 changed files with 258 additions and 51 deletions

View File

@@ -96,9 +96,9 @@ Note the `22050` sample rate -- this is the reccommended setting for GLava. Rest
## Using GLava with OBS
GLava installs an OBS plugin if support was enabled at compile-time. This should be enabled by default in Meson, but it is overridden to disabled in the `Makefile` for build compatibility.
GLava installs a plugin for rendering directly to an OBS scene, if support was enabled at compile-time. This is enabled by default in Meson, but it is overridden to disabled in the `Makefile` for build compatibility.
To use the plugin, simply select `GLava Source` from the source list in OBS and position the output accordingly. You can provide options to GLava in the source settings.
To use the plugin, simply select `GLava Direct Source` from the source list in OBS and position the output accordingly. You can provide options to GLava in the source properties.
Note that this only works for the default GLX builds of both OBS and GLava. This feature will not work if OBS was compiled with EGL for context creation, or if GLava is using GLFW.

View File

@@ -1,66 +1,263 @@
#include <stdlib.h>
#include <obs-module.h>
#include <obs.h>
#include <util/threading.h>
#include <util/platform.h>
#include <obs/obs-module.h>
#include <obs/obs.h>
#include <obs/util/threading.h>
#include <obs/util/platform.h>
#include "../glava/glava.h"
#include <X11/Xlib.h>
#pragma GCC visibility push(default)
OBS_DECLARE_MODULE();
#pragma GCC visibility pop
static glava_handle handle;
/* To access OBS's GL context and internal texture handles we need to define internal
OBS structures such that we can access these members. This is not API-stable, but
these structure layouts rarely change. */
/* OBS INTERNAL DEFS */
typedef struct __GLXcontextRec* GLXContext;
typedef XID GLXPixmap;
typedef XID GLXDrawable;
typedef XID GLXPbuffer;
struct gl_platform_internal {
Display *display;
GLXContext context;
GLXPbuffer pbuffer;
};
struct gs_device_internal {
struct gl_platform_internal* plat;
/* trailing members present */
};
struct gs_subsystem_internal {
void* module;
struct gs_device_internal* device;
/* trailing members present */
};
struct gs_texture {
struct gs_device_internal* device;
int type;
int format;
int gl_format;
int gl_target;
int gl_internal_format;
int gl_type;
unsigned int texture;
uint32_t levels;
bool is_dynamic;
bool is_render_target;
bool is_dummy;
bool gen_mipmaps;
void* cur_sampler;
void* fbo;
};
struct gs_texture_2d_internal {
struct gs_texture base;
uint32_t width;
uint32_t height;
/* trailing members present */
};
/* END OBS INTERNAL DEFS */
struct mod_state {
obs_source_t* source;
os_event_t* stop_signal;
pthread_t thread;
bool initialized;
gs_texture_t* gs_tex;
unsigned int old_tex;
struct {
const char* opts;
int w, h;
} cfg;
};
static const char* get_name(void* _) {
UNUSED_PARAMETER(_);
return "GLava Source";
return "GLava Direct Source";
}
static obs_properties_t* get_properties(void* _) {
UNUSED_PARAMETER(_);
obs_properties_t* props = obs_properties_create();
// (obs_properties_t *props, const char *name, const char *description, int min, int max, int step)
obs_properties_add_int (props, "width", "Output width", 0, 65535, 1);
obs_properties_add_int (props, "height", "Output height", 0, 65535, 1);
obs_properties_add_text(props, "options", "GLava options", OBS_TEXT_DEFAULT);
return props;
}
static uint32_t get_width(void* data) {
struct mod_state* s = (struct mod_state*) data;
return (uint32_t) s->cfg.w;
}
static uint32_t get_height(void* data) {
struct mod_state* s = (struct mod_state*) data;
return (uint32_t) s->cfg.h;
}
static void* work_thread(void* _) {
UNUSED_PARAMETER(_);
glava_entry(1, (char**) &"glava", &handle);
return NULL;
}
static void glava_join(void* data) {
struct mod_state* s = (struct mod_state*) data;
glava_terminate(&handle);
if (s->initialized) {
if (pthread_join(s->thread, NULL)) {
blog(LOG_ERROR, "Failed to join GLava thread");
return;
}
}
s->initialized = false;
if (s->gs_tex != NULL) {
obs_enter_graphics();
/* restore old GL texture */
((struct gs_texture_2d_internal*) s->gs_tex)->base.texture = s->old_tex;
gs_texture_destroy(s->gs_tex);
obs_leave_graphics();
s->gs_tex = NULL;
}
}
static void glava_start(void* data) {
struct mod_state* s = (struct mod_state*) data;
if (s->initialized) {
blog(LOG_ERROR, "Already initialized GLava thread");
return;
}
if (pthread_create(&s->thread, NULL, work_thread, s) != 0) {
blog(LOG_ERROR, "Failed to create GLava thread");
return;
}
s->initialized = true;
/* Obtain GLava's texture handle */
blog(LOG_INFO, "Waiting for GLava GL texture...");
glava_wait(&handle);
unsigned int g_tex = glava_tex(handle);
glava_sizereq(handle, 0, 0, s->cfg.w, s->cfg.h);
obs_enter_graphics();
/* Create a new high-level texture object */
s->gs_tex = gs_texture_create(s->cfg.w, s->cfg.h, GS_RGBA, 1, NULL, GS_DYNAMIC);
/* Re-assign the internal GL texture for the object */
s->old_tex = ((struct gs_texture_2d_internal*) s->gs_tex)->base.texture;
((struct gs_texture_2d_internal*) s->gs_tex)->base.texture = g_tex;
obs_leave_graphics();
blog(LOG_INFO, "GLava texture assigned");
}
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);
glava_join(s);
bfree(s);
}
}
static void* work_thread(void* data) {
return NULL;
static void update(void* data, obs_data_t* settings) {
struct mod_state* s = (struct mod_state*) data;
s->cfg.w = (int) obs_data_get_int(settings, "width");
s->cfg.h = (int) obs_data_get_int(settings, "height");
const char* opts = obs_data_get_string(settings, "options");
bool opts_changed = s->cfg.opts == NULL || strcmp(opts, s->cfg.opts) != 0;
s->cfg.opts = opts;
if (opts_changed) {
blog(LOG_INFO, "Updating GLava state");
glava_join(s);
glava_start(s);
} else {
glava_sizereq(handle, 0, 0, s->cfg.w, s->cfg.h);
((struct gs_texture_2d_internal*) s->gs_tex)->width = s->cfg.w;
((struct gs_texture_2d_internal*) s->gs_tex)->height = s->cfg.h;
}
}
static void video_render(void* data, gs_effect_t* effect) {
struct mod_state* s = (struct mod_state*) data;
if (s->gs_tex == NULL)
return;
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
gs_eparam_t* img = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(img, s->gs_tex);
while (gs_effect_loop(effect, "Draw"))
// gs_draw_sprite(s->gs_tex, 0, 0, 0);
obs_source_draw(s->gs_tex, 0, 0, 0, 0, true);
/*
while (gs_effect_loop(effect, "Draw")) {
obs_source_draw(s->gs_tex, 0, 0, 0, 0, 0);
}
*/
}
static void* create(obs_data_t* settings, obs_source_t* source) {
blog(LOG_INFO, "Initializing GLava OBS Plugin...");
struct mod_state* s = bzalloc(sizeof(struct mod_state));
s->source = source;
s->cfg = (typeof(s->cfg)) { .w = 512, .h = 256, .opts = NULL };
s->gs_tex = NULL;
s->initialized = false;
if (os_event_init(&s->stop_signal, OS_EVENT_TYPE_MANUAL) != 0) {
destroy(s);
struct obs_video_info ovi;
if (!obs_get_video_info(&ovi)) {
blog(LOG_ERROR, "Failed to obtain `obs_video_info`");
return NULL;
}
if (pthread_create(&s->thread, NULL, work_thread, s) != 0) {
destroy(s);
if (strncmp(ovi.graphics_module, "libobs-opengl", 13) != 0) {
blog(LOG_ERROR, "No GLX rendering context present");
return NULL;
}
s->initialized = true;
obs_enter_graphics();
struct gs_subsystem_internal* sub = (struct gs_subsystem_internal*) gs_get_context();
glava_assign_external_ctx(sub->device->plat->context);
obs_leave_graphics();
update(s, settings);
return s;
}
static struct obs_source_info glava_src = {
.id = "glava",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_ASYNC_VIDEO,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_DO_NOT_DUPLICATE,
.get_name = get_name,
.create = create,
.destroy = destroy
.destroy = destroy,
.update = update,
.video_render = video_render,
.get_width = get_width,
.get_height = get_height,
.get_properties = get_properties
};
__attribute__((visibility("default"))) bool obs_module_load(void) {

View File

@@ -250,11 +250,12 @@ __attribute__((visibility("default"))) void glava_wait(glava_handle* ref) {
nanosleep(&tv, NULL);
}
pthread_mutex_lock(&(*ref)->lock);
while ((*ref)->flag == false)
pthread_cond_wait(&(*ref)->cond, &(*ref)->lock);
pthread_mutex_unlock(&(*ref)->lock);
}
__attribute__((visibility("default"))) int glava_tex(glava_handle r) {
__attribute__((visibility("default"))) unsigned int glava_tex(glava_handle r) {
return r->off_tex;
}
@@ -267,14 +268,17 @@ __attribute__((visibility("default"))) void glava_sizereq(glava_handle r, int x,
/* Atomic terminate request */
__attribute__((visibility("default"))) void glava_terminate(glava_handle* ref) {
glava_handle store = __atomic_exchange_n(ref, NULL, __ATOMIC_SEQ_CST);
if (store)
__atomic_store_n(&store->alive, false, __ATOMIC_SEQ_CST);
}
/* Atomic reload request */
__attribute__((visibility("default"))) void glava_reload(glava_handle* ref) {
glava_handle store = __atomic_exchange_n(ref, NULL, __ATOMIC_SEQ_CST);
if (store) {
__atomic_store_n(&reload, true, __ATOMIC_SEQ_CST);
__atomic_store_n(&store->alive, false, __ATOMIC_SEQ_CST);
}
}

View File

@@ -22,6 +22,6 @@ __attribute__((visibility("default"))) void glava_terminate
__attribute__((visibility("default"))) void glava_reload (glava_handle* ref);
__attribute__((visibility("default"))) void glava_sizereq (glava_handle r, int x, int y, int w, int h);
__attribute__((visibility("default"))) void glava_wait (glava_handle* ref);
__attribute__((visibility("default"))) int glava_tex (glava_handle r);
__attribute__((visibility("default"))) unsigned int glava_tex (glava_handle r);
#endif /* _GLAVA_H */

View File

@@ -482,8 +482,6 @@ static void* create_and_bind(const char* name, const char* class,
apply_decorations(w->w);
XFree(vi);
XStoreName(display, w->w, name);
XSetWMProtocols(display, w->w, &ATOM_WM_DELETE_WINDOW, 1);
@@ -531,6 +529,8 @@ static void* create_and_bind(const char* name, const char* class,
if (!transparent)
XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask);
XFree(vi);
return w;
}

View File

@@ -821,7 +821,8 @@ struct glava_renderer* rd_new(const char** paths, const char* entry,
.off_tex = 0,
.lock = PTHREAD_MUTEX_INITIALIZER,
.cond = PTHREAD_COND_INITIALIZER,
.sizereq_flag = 0
.sizereq_flag = 0,
.flag = false
};
pthread_mutex_lock(&r->lock);
@@ -1502,6 +1503,7 @@ struct glava_renderer* rd_new(const char** paths, const char* entry,
gl->wcb->get_fbsize(gl->w, &w, &h);
setup_sfbo(&gl->off_sfbo, w, h);
r->off_tex = gl->off_sfbo.tex;
r->flag = true;
pthread_cond_signal(&r->cond);
pthread_mutex_unlock(&r->lock);
}
@@ -1853,10 +1855,8 @@ bool rd_update(struct glava_renderer* r, float* lb, float* rb, size_t bsz, bool
/* Bind framebuffer if this is not the final pass */
if (current->indirect)
glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
if (!current->indirect && (gl->test_mode || gl->wcb->offscreen())) {
else if (gl->test_mode || gl->wcb->offscreen())
glBindFramebuffer(GL_FRAMEBUFFER, gl->off_sfbo.fbo);
}
glClear(GL_COLOR_BUFFER_BIT);
@@ -2027,8 +2027,9 @@ bool rd_update(struct glava_renderer* r, float* lb, float* rb, size_t bsz, bool
glUseProgram(current->shader);
if (current->indirect)
glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
else
glBindFramebuffer(GL_FRAMEBUFFER, 0);
else if (gl->test_mode || gl->wcb->offscreen())
glBindFramebuffer(GL_FRAMEBUFFER, gl->off_sfbo.fbo);
else glBindFramebuffer(GL_FRAMEBUFFER, 0);
tex = sm->tex; /* replace input texture with our processed one */
}
@@ -2065,8 +2066,12 @@ bool rd_update(struct glava_renderer* r, float* lb, float* rb, size_t bsz, bool
drawoverlay(&gl->overlay); /* Fullscreen quad (actually just two triangles) */
/* Reset some state */
if (current->indirect)
if (current->indirect) {
if (gl->test_mode || gl->wcb->offscreen())
glBindFramebuffer(GL_FRAMEBUFFER, gl->off_sfbo.fbo);
else
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
glUseProgram(0);
prev = current;

View File

@@ -12,9 +12,10 @@ typedef struct glava_renderer {
bool mirror_input;
size_t bufsize_request, rate_request, samplesize_request;
char* audio_source_request;
int off_tex; /* final GL texture for offscreen rendering */
unsigned 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 */
bool flag; /* vadility flag for reading from offscreen tecture */
volatile struct {
int x, y, w, h;
} sizereq;