obs-studio/plugins/linux-capture/xcompcap-main.cpp
jp9000 e20ec366b2 Make capture sources w/o alpha use opaque effect
This fixes an issue primarily with filter rendering: when capturing
windows and displays, their alpha channel is almost always 0, causing
the image to be completely invisible unintentionally.  The original fix
for this for many sources was just to turn off the blending, which would
be fine if you're not rendering any filters, but filters will render to
render targets first, and that lack of alpha will end up carrying over
in to the final image.

This doesn't apply to any mac captures because mac actually seems to set
the alpha channel to 1.
2015-03-22 19:18:05 -07:00

534 lines
12 KiB
C++

#include <glad/glad.h>
#include <glad/glad_glx.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xcomposite.h>
#include <pthread.h>
#include <vector>
#include <obs-module.h>
#include <graphics/vec4.h>
#include <util/platform.h>
#include "xcompcap-main.hpp"
#include "xcompcap-helper.hpp"
#include "xcursor.h"
#define xdisp (XCompcap::disp())
#define WIN_STRING_DIV "\r\n"
bool XCompcapMain::init()
{
if (!xdisp) {
blog(LOG_ERROR, "failed opening display");
return false;
}
int eventBase, errorBase;
if (!XCompositeQueryExtension(xdisp, &eventBase, &errorBase)) {
blog(LOG_ERROR, "Xcomposite extension not supported");
return false;
}
int major = 0, minor = 2;
XCompositeQueryVersion(xdisp, &major, &minor);
if (major == 0 && minor < 2) {
blog(LOG_ERROR, "Xcomposite extension is too old: %d.%d < 0.2",
major, minor);
return false;
}
return true;
}
void XCompcapMain::deinit()
{
XCompcap::cleanupDisplay();
}
obs_properties_t *XCompcapMain::properties()
{
obs_properties_t *props = obs_properties_create();
obs_property_t *wins = obs_properties_add_list(props, "capture_window",
obs_module_text("Window"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
for (Window win: XCompcap::getTopLevelWindows()) {
std::string wname = XCompcap::getWindowName(win);
std::string progpath = XCompcap::getWindowCommand(win);
std::string winid = std::to_string((long long)win);
std::string desc =
(winid + WIN_STRING_DIV + wname +
WIN_STRING_DIV + progpath);
obs_property_list_add_string(wins, wname.c_str(),
desc.c_str());
}
obs_properties_add_int(props, "cut_top", obs_module_text("CropTop"),
0, 4096, 1);
obs_properties_add_int(props, "cut_left", obs_module_text("CropLeft"),
0, 4096, 1);
obs_properties_add_int(props, "cut_right", obs_module_text("CropRight"),
0, 4096, 1);
obs_properties_add_int(props, "cut_bot", obs_module_text("CropBottom"),
0, 4096, 1);
obs_properties_add_bool(props, "swap_redblue",
obs_module_text("SwapRedBlue"));
obs_properties_add_bool(props, "lock_x", obs_module_text("LockX"));
obs_properties_add_bool(props, "show_cursor",
obs_module_text("CaptureCursor"));
obs_properties_add_bool(props, "include_border",
obs_module_text("IncludeXBorder"));
return props;
}
void XCompcapMain::defaults(obs_data_t *settings)
{
obs_data_set_default_string(settings, "capture_window", "");
obs_data_set_default_int(settings, "cut_top", 0);
obs_data_set_default_int(settings, "cut_left", 0);
obs_data_set_default_int(settings, "cut_right", 0);
obs_data_set_default_int(settings, "cut_bot", 0);
obs_data_set_default_bool(settings, "swap_redblue", false);
obs_data_set_default_bool(settings, "lock_x", false);
obs_data_set_default_bool(settings, "show_cursor", true);
obs_data_set_default_bool(settings, "include_border", false);
}
struct XCompcapMain_private
{
XCompcapMain_private()
:win(0)
,cut_top(0), cur_cut_top(0)
,cut_left(0), cur_cut_left(0)
,cut_right(0), cur_cut_right(0)
,cut_bot(0), cur_cut_bot(0)
,inverted(false)
,width(0),height(0)
,pixmap(0)
,glxpixmap(0)
,tex(0)
,gltex(0)
{
pthread_mutexattr_init(&lockattr);
pthread_mutexattr_settype(&lockattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &lockattr);
}
~XCompcapMain_private()
{
pthread_mutex_destroy(&lock);
pthread_mutexattr_destroy(&lockattr);
}
obs_source_t *source;
Window win;
int cut_top, cur_cut_top;
int cut_left, cur_cut_left;
int cut_right, cur_cut_right;
int cut_bot, cur_cut_bot;
bool inverted;
bool swapRedBlue;
bool lockX;
bool include_border;
uint32_t width;
uint32_t height;
uint32_t border;
Pixmap pixmap;
GLXPixmap glxpixmap;
gs_texture_t *tex;
gs_texture_t *gltex;
pthread_mutex_t lock;
pthread_mutexattr_t lockattr;
bool show_cursor = true;
bool cursor_outside = false;
xcursor_t *cursor = nullptr;
};
XCompcapMain::XCompcapMain(obs_data_t *settings, obs_source_t *source)
{
p = new XCompcapMain_private;
p->source = source;
obs_enter_graphics();
p->cursor = xcursor_init(xdisp);
obs_leave_graphics();
updateSettings(settings);
}
static void xcc_cleanup(XCompcapMain_private *p);
XCompcapMain::~XCompcapMain()
{
ObsGsContextHolder obsctx;
if (p->tex) {
gs_texture_destroy(p->tex);
p->tex = 0;
}
xcc_cleanup(p);
if (p->cursor) {
xcursor_destroy(p->cursor);
p->cursor = nullptr;
}
delete p;
}
static Window getWindowFromString(std::string wstr)
{
if (wstr == "") {
return XCompcap::getTopLevelWindows().front();
}
if (wstr.substr(0, 4) == "root") {
int i = std::stoi("0" + wstr.substr(4));
return RootWindow(xdisp, i);
}
size_t firstMark = wstr.find(WIN_STRING_DIV);
if (firstMark == std::string::npos)
return (Window)std::stol(wstr);
std::string widstr = wstr.substr(0, firstMark);
Window wid = (Window)std::stol(widstr);
wstr = wstr.substr(firstMark + strlen(WIN_STRING_DIV));
size_t lastMark = wstr.rfind(WIN_STRING_DIV);
std::string wname = wstr.substr(0, lastMark);
Window matchedNameWin = wid;
for (Window cwin: XCompcap::getTopLevelWindows()) {
std::string cwinname = XCompcap::getWindowName(cwin);
if (cwin == wid && wname == cwinname)
return wid;
if (wname == cwinname)
matchedNameWin = cwin;
}
return matchedNameWin;
}
static void xcc_cleanup(XCompcapMain_private *p)
{
if (p->gltex) {
gs_texture_destroy(p->gltex);
p->gltex = 0;
}
if (p->glxpixmap) {
glXDestroyPixmap(xdisp, p->glxpixmap);
p->glxpixmap = 0;
}
if (p->pixmap) {
XFreePixmap(xdisp, p->pixmap);
p->pixmap = 0;
}
if (p->win) {
XCompositeUnredirectWindow(xdisp, p->win,
CompositeRedirectAutomatic);
XSelectInput(xdisp, p->win, 0);
p->win = 0;
}
}
void XCompcapMain::updateSettings(obs_data_t *settings)
{
PLock lock(&p->lock);
XErrorLock xlock;
ObsGsContextHolder obsctx;
blog(LOG_DEBUG, "Settings updating");
Window prevWin = p->win;
xcc_cleanup(p);
if (settings) {
const char *windowName = obs_data_get_string(settings,
"capture_window");
p->win = getWindowFromString(windowName);
p->cut_top = obs_data_get_int(settings, "cut_top");
p->cut_left = obs_data_get_int(settings, "cut_left");
p->cut_right = obs_data_get_int(settings, "cut_right");
p->cut_bot = obs_data_get_int(settings, "cut_bot");
p->lockX = obs_data_get_bool(settings, "lock_x");
p->swapRedBlue = obs_data_get_bool(settings, "swap_redblue");
p->show_cursor = obs_data_get_bool(settings, "show_cursor");
p->include_border = obs_data_get_bool(settings, "include_border");
} else {
p->win = prevWin;
}
xlock.resetError();
XCompositeRedirectWindow(xdisp, p->win, CompositeRedirectAutomatic);
if (xlock.gotError()) {
blog(LOG_ERROR, "XCompositeRedirectWindow failed: %s",
xlock.getErrorText().c_str());
return;
}
XSelectInput(xdisp, p->win, StructureNotifyMask | ExposureMask);
XSync(xdisp, 0);
XWindowAttributes attr;
if (!XGetWindowAttributes(xdisp, p->win, &attr)) {
p->win = 0;
p->width = 0;
p->height = 0;
return;
}
if (p->cursor && p->show_cursor) {
Window child;
int x, y;
XTranslateCoordinates(xdisp, p->win, attr.root, 0, 0, &x, &y,
&child);
xcursor_offset(p->cursor, x, y);
}
gs_color_format cf = GS_RGBA;
p->border = attr.border_width;
if (p->include_border) {
p->width = attr.width + p->border * 2;
p->height = attr.height + p->border * 2;
} else {
p->width = attr.width;
p->height = attr.height;
}
if (p->cut_top + p->cut_bot < (int)p->height) {
p->cur_cut_top = p->cut_top;
p->cur_cut_bot = p->cut_bot;
} else {
p->cur_cut_top = 0;
p->cur_cut_bot = 0;
}
if (p->cut_left + p->cut_right < (int)p->width) {
p->cur_cut_left = p->cut_left;
p->cur_cut_right = p->cut_right;
} else {
p->cur_cut_left = 0;
p->cur_cut_right = 0;
}
if (p->tex)
gs_texture_destroy(p->tex);
uint8_t *texData = new uint8_t[width() * height() * 4];
for (unsigned int i = 0; i < width() * height() * 4; i += 4) {
texData[i + 0] = p->swapRedBlue ? 0 : 0xFF;
texData[i + 1] = 0;
texData[i + 2] = p->swapRedBlue ? 0xFF : 0;
texData[i + 3] = 0xFF;
}
const uint8_t* texDataArr[] = { texData, 0 };
p->tex = gs_texture_create(width(), height(), cf, 1,
texDataArr, 0);
delete[] texData;
if (p->swapRedBlue) {
GLuint tex = *(GLuint*)gs_texture_get_obj(p->tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glBindTexture(GL_TEXTURE_2D, 0);
}
const int attrs[] =
{
GLX_BIND_TO_TEXTURE_RGBA_EXT, GL_TRUE,
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
GLX_DOUBLEBUFFER, GL_FALSE,
None
};
int nelem = 0;
GLXFBConfig* configs = glXChooseFBConfig(xdisp,
XCompcap::getRootWindowScreen(attr.root),
attrs, &nelem);
if (nelem <= 0) {
blog(LOG_ERROR, "no matching fb config found");
p->win = 0;
p->height = 0;
p->width = 0;
return;
}
glXGetFBConfigAttrib(xdisp, configs[0], GLX_Y_INVERTED_EXT, &nelem);
p->inverted = nelem != 0;
xlock.resetError();
p->pixmap = XCompositeNameWindowPixmap(xdisp, p->win);
if (xlock.gotError()) {
blog(LOG_ERROR, "XCompositeNameWindowPixmap failed: %s",
xlock.getErrorText().c_str());
p->pixmap = 0;
XFree(configs);
return;
}
const int attribs[] =
{
GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGBA_EXT,
None
};
p->glxpixmap = glXCreatePixmap(xdisp, configs[0], p->pixmap, attribs);
if (xlock.gotError()) {
blog(LOG_ERROR, "glXCreatePixmap failed: %s",
xlock.getErrorText().c_str());
XFreePixmap(xdisp, p->pixmap);
XFree(configs);
p->pixmap = 0;
p->glxpixmap = 0;
return;
}
XFree(configs);
p->gltex = gs_texture_create(p->width, p->height, cf, 1, 0,
GS_GL_DUMMYTEX);
GLuint gltex = *(GLuint*)gs_texture_get_obj(p->gltex);
glBindTexture(GL_TEXTURE_2D, gltex);
glXBindTexImageEXT(xdisp, p->glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
void XCompcapMain::tick(float seconds)
{
UNUSED_PARAMETER(seconds);
if (!obs_source_showing(p->source))
return;
PLock lock(&p->lock, true);
if (!lock.isLocked())
return;
XCompcap::processEvents();
if (XCompcap::windowWasReconfigured(p->win))
updateSettings(0);
if (!p->tex || !p->gltex)
return;
obs_enter_graphics();
if (p->lockX) {
XLockDisplay(xdisp);
XSync(xdisp, 0);
}
if (p->include_border) {
gs_copy_texture_region(
p->tex, 0, 0,
p->gltex,
p->cur_cut_left,
p->cur_cut_top,
width(), height());
} else {
gs_copy_texture_region(
p->tex, 0, 0,
p->gltex,
p->cur_cut_left + p->border,
p->cur_cut_top + p->border,
width(), height());
}
if (p->cursor && p->show_cursor) {
xcursor_tick(p->cursor);
p->cursor_outside =
p->cursor->x < p->cur_cut_left ||
p->cursor->y < p->cur_cut_top ||
p->cursor->x > int(p->width - p->cur_cut_right) ||
p->cursor->y > int(p->height - p->cur_cut_bot);
}
if (p->lockX)
XUnlockDisplay(xdisp);
obs_leave_graphics();
}
void XCompcapMain::render(gs_effect_t *effect)
{
PLock lock(&p->lock, true);
effect = obs_get_opaque_effect();
if (!lock.isLocked() || !p->tex)
return;
gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(image, p->tex);
while (gs_effect_loop(effect, "Draw")) {
gs_draw_sprite(p->tex, 0, 0, 0);
}
if (p->cursor && p->gltex && p->show_cursor && !p->cursor_outside) {
effect = obs_get_default_effect();
while (gs_effect_loop(effect, "Draw")) {
xcursor_render(p->cursor);
}
}
}
uint32_t XCompcapMain::width()
{
return p->width - p->cur_cut_left - p->cur_cut_right;
}
uint32_t XCompcapMain::height()
{
return p->height - p->cur_cut_bot - p->cur_cut_top;
}