界面与品牌 - 默认 UI 语言固定为英文,不再跟随系统区域设置自动切换(frontend/OBSApp.cpp),用户仍可在设置中手动切换 - 程序图标更换为 CAIIC logo:exe 图标 obs-studio.ico 及界面/托盘/About 图标 obs.png - About 窗口精简为 logo + 名称 + 版本号,移除项目介绍、赞助名单、Authors/License 标签页 - 版本号定为 1.0.0(构建参数 OBS_VERSION_OVERRIDE=1.0.0) 分辨率支持 - 移除首次启动将画布强制限制为 1920x1080 的逻辑,默认使用显示器原生分辨率 - 设置-视频 与自动配置向导新增 3840x2160 / 2560x1440 预设 - 自动配置向导性能测试上限放宽至 4K60(软硬件编码数据率上限及起始分辨率钳制) 插件 - 集成 obs-shaderfilter 2.6.0(绿幕抠图/自定义着色器滤镜),并适配新版 libobs 的 gs_image_file_ex API - 补齐缺失的源码:obs-browser、obs-websocket、libdshowcapture(含 external/capture-device-support) 工程 - 新增 .gitignore,排除 deploy/ 发布目录与构建日志
680 lines
18 KiB
C++
680 lines
18 KiB
C++
/******************************************************************************
|
|
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
|
|
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
******************************************************************************/
|
|
|
|
#include "obs-browser-source.hpp"
|
|
#include "browser-client.hpp"
|
|
#include "browser-scheme.hpp"
|
|
#include "wide-string.hpp"
|
|
#include <nlohmann/json.hpp>
|
|
#include <util/threading.h>
|
|
#include <QApplication>
|
|
#include <util/dstr.h>
|
|
#include <functional>
|
|
#include <thread>
|
|
#include <mutex>
|
|
|
|
#ifdef __linux__
|
|
#include "linux-keyboard-helpers.hpp"
|
|
#endif
|
|
|
|
#ifdef ENABLE_BROWSER_QT_LOOP
|
|
#include <QEventLoop>
|
|
#include <QThread>
|
|
#endif
|
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
|
#include "drm-format.hpp"
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
extern os_event_t *cef_started_event;
|
|
extern bool QueueCEFTask(std::function<void()> task);
|
|
|
|
static mutex browser_list_mutex;
|
|
static BrowserSource *first_browser = nullptr;
|
|
|
|
static void SendBrowserVisibility(CefRefPtr<CefBrowser> browser, bool isVisible)
|
|
{
|
|
if (!browser)
|
|
return;
|
|
|
|
if (isVisible) {
|
|
browser->GetHost()->WasResized();
|
|
browser->GetHost()->WasHidden(false);
|
|
browser->GetHost()->Invalidate(PET_VIEW);
|
|
} else {
|
|
browser->GetHost()->WasHidden(true);
|
|
}
|
|
|
|
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("Visibility");
|
|
CefRefPtr<CefListValue> args = msg->GetArgumentList();
|
|
args->SetBool(0, isVisible);
|
|
SendBrowserProcessMessage(browser, PID_RENDERER, msg);
|
|
}
|
|
|
|
void DispatchJSEvent(std::string eventName, std::string jsonString, BrowserSource *browser = nullptr);
|
|
|
|
BrowserSource::BrowserSource(obs_data_t *, obs_source_t *source_) : source(source_)
|
|
{
|
|
|
|
/* Register Refresh hotkey */
|
|
auto refreshFunction = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
|
|
if (pressed) {
|
|
BrowserSource *bs = (BrowserSource *)data;
|
|
bs->Refresh();
|
|
}
|
|
};
|
|
|
|
obs_hotkey_register_source(source, "ObsBrowser.Refresh", obs_module_text("RefreshNoCache"), refreshFunction,
|
|
(void *)this);
|
|
|
|
auto jsEventFunction = [](void *p, calldata_t *calldata) {
|
|
const auto eventName = calldata_string(calldata, "eventName");
|
|
if (!eventName)
|
|
return;
|
|
auto jsonString = calldata_string(calldata, "jsonString");
|
|
if (!jsonString)
|
|
jsonString = "null";
|
|
DispatchJSEvent(eventName, jsonString, (BrowserSource *)p);
|
|
};
|
|
|
|
proc_handler_t *ph = obs_source_get_proc_handler(source);
|
|
proc_handler_add(ph, "void javascript_event(string eventName, string jsonString)", jsEventFunction,
|
|
(void *)this);
|
|
|
|
/* defer update */
|
|
obs_source_update(source, nullptr);
|
|
|
|
lock_guard<mutex> lock(browser_list_mutex);
|
|
p_prev_next = &first_browser;
|
|
next = first_browser;
|
|
if (first_browser)
|
|
first_browser->p_prev_next = &next;
|
|
first_browser = this;
|
|
}
|
|
|
|
static void ActuallyCloseBrowser(CefRefPtr<CefBrowser> cefBrowser)
|
|
{
|
|
CefRefPtr<CefClient> client = cefBrowser->GetHost()->GetClient();
|
|
BrowserClient *bc = reinterpret_cast<BrowserClient *>(client.get());
|
|
if (bc) {
|
|
bc->bs = nullptr;
|
|
}
|
|
|
|
/*
|
|
* This stops rendering
|
|
* http://magpcss.org/ceforum/viewtopic.php?f=6&t=12079
|
|
* https://bitbucket.org/chromiumembedded/cef/issues/1363/washidden-api-got-broken-on-branch-2062)
|
|
*/
|
|
cefBrowser->GetHost()->WasHidden(true);
|
|
cefBrowser->GetHost()->CloseBrowser(true);
|
|
}
|
|
|
|
BrowserSource::~BrowserSource()
|
|
{
|
|
if (cefBrowser)
|
|
ActuallyCloseBrowser(cefBrowser);
|
|
}
|
|
|
|
void BrowserSource::Destroy()
|
|
{
|
|
destroying = true;
|
|
DestroyTextures();
|
|
|
|
lock_guard<mutex> lock(browser_list_mutex);
|
|
if (next)
|
|
next->p_prev_next = p_prev_next;
|
|
*p_prev_next = next;
|
|
|
|
QueueCEFTask([this]() { delete this; });
|
|
}
|
|
|
|
void BrowserSource::ExecuteOnBrowser(BrowserFunc func, bool async)
|
|
{
|
|
if (!async) {
|
|
#ifdef ENABLE_BROWSER_QT_LOOP
|
|
if (QThread::currentThread() == qApp->thread()) {
|
|
if (!!cefBrowser)
|
|
func(cefBrowser);
|
|
return;
|
|
}
|
|
#endif
|
|
os_event_t *finishedEvent;
|
|
os_event_init(&finishedEvent, OS_EVENT_TYPE_AUTO);
|
|
bool success = QueueCEFTask([&]() {
|
|
if (!!cefBrowser)
|
|
func(cefBrowser);
|
|
os_event_signal(finishedEvent);
|
|
});
|
|
if (success) {
|
|
os_event_wait(finishedEvent);
|
|
}
|
|
os_event_destroy(finishedEvent);
|
|
} else {
|
|
CefRefPtr<CefBrowser> browser = GetBrowser();
|
|
if (!!browser) {
|
|
#ifdef ENABLE_BROWSER_QT_LOOP
|
|
QueueBrowserTask(cefBrowser, func);
|
|
#else
|
|
QueueCEFTask([func, browser]() { func(browser); });
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
bool BrowserSource::CreateBrowser()
|
|
{
|
|
return QueueCEFTask([this]() {
|
|
#ifdef ENABLE_BROWSER_SHARED_TEXTURE
|
|
if (hwaccel) {
|
|
obs_enter_graphics();
|
|
#if defined(__APPLE__) || defined(_WIN32)
|
|
tex_sharing_avail = gs_shared_texture_available();
|
|
#else
|
|
tex_sharing_avail = obs_cef_all_drm_formats_supported();
|
|
#endif
|
|
obs_leave_graphics();
|
|
}
|
|
#else
|
|
bool hwaccel = false;
|
|
#endif
|
|
|
|
CefRefPtr<BrowserClient> browserClient =
|
|
new BrowserClient(this, hwaccel && tex_sharing_avail, reroute_audio, webpage_control_level);
|
|
|
|
CefWindowInfo windowInfo;
|
|
windowInfo.bounds.width = width;
|
|
windowInfo.bounds.height = height;
|
|
windowInfo.windowless_rendering_enabled = true;
|
|
|
|
#ifdef ENABLE_BROWSER_SHARED_TEXTURE
|
|
windowInfo.shared_texture_enabled = hwaccel;
|
|
#endif
|
|
|
|
CefBrowserSettings cefBrowserSettings;
|
|
|
|
#ifdef ENABLE_BROWSER_SHARED_TEXTURE
|
|
#ifdef BROWSER_EXTERNAL_BEGIN_FRAME_ENABLED
|
|
if (!fps_custom) {
|
|
windowInfo.external_begin_frame_enabled = true;
|
|
cefBrowserSettings.windowless_frame_rate = 0;
|
|
} else {
|
|
cefBrowserSettings.windowless_frame_rate = fps;
|
|
}
|
|
#else
|
|
struct obs_video_info ovi;
|
|
obs_get_video_info(&ovi);
|
|
canvas_fps = (double)ovi.fps_num / (double)ovi.fps_den;
|
|
cefBrowserSettings.windowless_frame_rate = (fps_custom) ? fps : canvas_fps;
|
|
#endif
|
|
#else
|
|
cefBrowserSettings.windowless_frame_rate = fps;
|
|
#endif
|
|
|
|
cefBrowserSettings.default_font_size = 16;
|
|
cefBrowserSettings.default_fixed_font_size = 16;
|
|
|
|
auto browser = CefBrowserHost::CreateBrowserSync(windowInfo, browserClient, url, cefBrowserSettings,
|
|
CefRefPtr<CefDictionaryValue>(), nullptr);
|
|
|
|
SetBrowser(browser);
|
|
|
|
if (reroute_audio)
|
|
cefBrowser->GetHost()->SetAudioMuted(true);
|
|
if (obs_source_showing(source))
|
|
is_showing = true;
|
|
|
|
SendBrowserVisibility(cefBrowser, is_showing);
|
|
});
|
|
}
|
|
|
|
void BrowserSource::DestroyBrowser()
|
|
{
|
|
ExecuteOnBrowser(ActuallyCloseBrowser, true);
|
|
SetBrowser(nullptr);
|
|
}
|
|
|
|
void BrowserSource::SendMouseClick(const struct obs_mouse_event *event, int32_t type, bool mouse_up,
|
|
uint32_t click_count)
|
|
{
|
|
uint32_t modifiers = event->modifiers;
|
|
int32_t x = event->x;
|
|
int32_t y = event->y;
|
|
|
|
ExecuteOnBrowser(
|
|
[modifiers, x, y, type, mouse_up, click_count](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefMouseEvent e;
|
|
e.modifiers = modifiers;
|
|
e.x = x;
|
|
e.y = y;
|
|
CefBrowserHost::MouseButtonType buttonType = (CefBrowserHost::MouseButtonType)type;
|
|
cefBrowser->GetHost()->SendMouseClickEvent(e, buttonType, mouse_up, click_count);
|
|
},
|
|
true);
|
|
}
|
|
|
|
void BrowserSource::SendMouseMove(const struct obs_mouse_event *event, bool mouse_leave)
|
|
{
|
|
uint32_t modifiers = event->modifiers;
|
|
int32_t x = event->x;
|
|
int32_t y = event->y;
|
|
|
|
ExecuteOnBrowser(
|
|
[modifiers, x, y, mouse_leave](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefMouseEvent e;
|
|
e.modifiers = modifiers;
|
|
e.x = x;
|
|
e.y = y;
|
|
cefBrowser->GetHost()->SendMouseMoveEvent(e, mouse_leave);
|
|
},
|
|
true);
|
|
}
|
|
|
|
void BrowserSource::SendMouseWheel(const struct obs_mouse_event *event, int x_delta, int y_delta)
|
|
{
|
|
uint32_t modifiers = event->modifiers;
|
|
int32_t x = event->x;
|
|
int32_t y = event->y;
|
|
|
|
ExecuteOnBrowser(
|
|
[modifiers, x, y, x_delta, y_delta](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefMouseEvent e;
|
|
e.modifiers = modifiers;
|
|
e.x = x;
|
|
e.y = y;
|
|
cefBrowser->GetHost()->SendMouseWheelEvent(e, x_delta, y_delta);
|
|
},
|
|
true);
|
|
}
|
|
|
|
void BrowserSource::SendFocus(bool focus)
|
|
{
|
|
ExecuteOnBrowser([focus](CefRefPtr<CefBrowser> cefBrowser) { cefBrowser->GetHost()->SetFocus(focus); }, true);
|
|
}
|
|
|
|
void BrowserSource::SendKeyClick(const struct obs_key_event *event, bool key_up)
|
|
{
|
|
if (destroying)
|
|
return;
|
|
|
|
std::string text = event->text;
|
|
#ifdef __linux__
|
|
uint32_t native_vkey = KeyboardCodeFromXKeysym(event->native_vkey);
|
|
uint32_t modifiers = event->native_modifiers;
|
|
#elif defined(_WIN32) || defined(__APPLE__)
|
|
uint32_t native_vkey = event->native_vkey;
|
|
uint32_t modifiers = event->modifiers;
|
|
#else
|
|
uint32_t native_vkey = event->native_vkey;
|
|
uint32_t native_scancode = event->native_scancode;
|
|
uint32_t modifiers = event->native_modifiers;
|
|
#endif
|
|
|
|
ExecuteOnBrowser(
|
|
[native_vkey, key_up, text, modifiers](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefKeyEvent e;
|
|
e.windows_key_code = native_vkey;
|
|
#ifdef __APPLE__
|
|
e.native_key_code = native_vkey;
|
|
#endif
|
|
|
|
e.type = key_up ? KEYEVENT_KEYUP : KEYEVENT_RAWKEYDOWN;
|
|
|
|
if (!text.empty()) {
|
|
wstring wide = to_wide(text);
|
|
if (wide.size())
|
|
e.character = wide[0];
|
|
}
|
|
|
|
//e.native_key_code = native_vkey;
|
|
e.modifiers = modifiers;
|
|
|
|
cefBrowser->GetHost()->SendKeyEvent(e);
|
|
if (!text.empty() && !key_up) {
|
|
e.type = KEYEVENT_CHAR;
|
|
#ifdef __linux__
|
|
e.windows_key_code = KeyboardCodeFromXKeysym(e.character);
|
|
#elif defined(_WIN32)
|
|
e.windows_key_code = e.character;
|
|
#elif !defined(__APPLE__)
|
|
e.native_key_code = native_scancode;
|
|
#endif
|
|
cefBrowser->GetHost()->SendKeyEvent(e);
|
|
}
|
|
},
|
|
true);
|
|
}
|
|
|
|
void BrowserSource::SetShowing(bool showing)
|
|
{
|
|
if (destroying)
|
|
return;
|
|
|
|
is_showing = showing;
|
|
|
|
if (shutdown_on_invisible) {
|
|
if (showing) {
|
|
Update();
|
|
} else {
|
|
DestroyBrowser();
|
|
}
|
|
} else {
|
|
ExecuteOnBrowser(
|
|
[showing](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("Visibility");
|
|
CefRefPtr<CefListValue> args = msg->GetArgumentList();
|
|
args->SetBool(0, showing);
|
|
SendBrowserProcessMessage(cefBrowser, PID_RENDERER, msg);
|
|
},
|
|
true);
|
|
nlohmann::json json;
|
|
json["visible"] = showing;
|
|
DispatchJSEvent("obsSourceVisibleChanged", json.dump(), this);
|
|
#if defined(BROWSER_EXTERNAL_BEGIN_FRAME_ENABLED) && defined(ENABLE_BROWSER_SHARED_TEXTURE)
|
|
if (showing && !fps_custom) {
|
|
reset_frame = false;
|
|
}
|
|
#endif
|
|
|
|
SendBrowserVisibility(cefBrowser, showing);
|
|
|
|
if (showing)
|
|
return;
|
|
|
|
obs_enter_graphics();
|
|
|
|
if (!hwaccel && texture) {
|
|
DestroyTextures();
|
|
}
|
|
|
|
obs_leave_graphics();
|
|
}
|
|
}
|
|
|
|
void BrowserSource::SetActive(bool active)
|
|
{
|
|
ExecuteOnBrowser(
|
|
[active](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("Active");
|
|
CefRefPtr<CefListValue> args = msg->GetArgumentList();
|
|
args->SetBool(0, active);
|
|
SendBrowserProcessMessage(cefBrowser, PID_RENDERER, msg);
|
|
},
|
|
true);
|
|
nlohmann::json json;
|
|
json["active"] = active;
|
|
DispatchJSEvent("obsSourceActiveChanged", json.dump(), this);
|
|
}
|
|
|
|
void BrowserSource::Refresh()
|
|
{
|
|
ExecuteOnBrowser([](CefRefPtr<CefBrowser> cefBrowser) { cefBrowser->ReloadIgnoreCache(); }, true);
|
|
}
|
|
|
|
void BrowserSource::SetBrowser(CefRefPtr<CefBrowser> b)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> auto_lock(lockBrowser);
|
|
cefBrowser = b;
|
|
}
|
|
|
|
CefRefPtr<CefBrowser> BrowserSource::GetBrowser()
|
|
{
|
|
std::lock_guard<std::recursive_mutex> auto_lock(lockBrowser);
|
|
return cefBrowser;
|
|
}
|
|
|
|
#ifdef ENABLE_BROWSER_SHARED_TEXTURE
|
|
#ifdef BROWSER_EXTERNAL_BEGIN_FRAME_ENABLED
|
|
inline void BrowserSource::SignalBeginFrame()
|
|
{
|
|
if (reset_frame) {
|
|
ExecuteOnBrowser(
|
|
[](CefRefPtr<CefBrowser> cefBrowser) { cefBrowser->GetHost()->SendExternalBeginFrame(); },
|
|
true);
|
|
|
|
reset_frame = false;
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
void BrowserSource::Update(obs_data_t *settings)
|
|
{
|
|
if (settings) {
|
|
bool n_is_local;
|
|
int n_width;
|
|
int n_height;
|
|
bool n_fps_custom;
|
|
int n_fps;
|
|
bool n_shutdown;
|
|
bool n_restart;
|
|
bool n_reroute;
|
|
ControlLevel n_webpage_control_level;
|
|
std::string n_url;
|
|
std::string n_css;
|
|
|
|
n_is_local = obs_data_get_bool(settings, "is_local_file");
|
|
n_width = (int)obs_data_get_int(settings, "width");
|
|
n_height = (int)obs_data_get_int(settings, "height");
|
|
n_fps_custom = obs_data_get_bool(settings, "fps_custom");
|
|
n_fps = (int)obs_data_get_int(settings, "fps");
|
|
n_shutdown = obs_data_get_bool(settings, "shutdown");
|
|
n_restart = obs_data_get_bool(settings, "restart_when_active");
|
|
n_css = obs_data_get_string(settings, "css");
|
|
n_url = obs_data_get_string(settings, n_is_local ? "local_file" : "url");
|
|
n_reroute = obs_data_get_bool(settings, "reroute_audio");
|
|
n_webpage_control_level =
|
|
static_cast<ControlLevel>(obs_data_get_int(settings, "webpage_control_level"));
|
|
|
|
if (n_is_local && !n_url.empty()) {
|
|
n_url = CefURIEncode(n_url, false);
|
|
|
|
#ifdef _WIN32
|
|
size_t slash = n_url.find("%2F");
|
|
size_t colon = n_url.find("%3A");
|
|
|
|
if (slash != std::string::npos && colon != std::string::npos && colon < slash)
|
|
n_url.replace(colon, 3, ":");
|
|
#endif
|
|
|
|
while (n_url.find("%5C") != std::string::npos)
|
|
n_url.replace(n_url.find("%5C"), 3, "/");
|
|
|
|
while (n_url.find("%2F") != std::string::npos)
|
|
n_url.replace(n_url.find("%2F"), 3, "/");
|
|
|
|
// Local files are routed through our custom scheme handler to give them acess to other local files
|
|
n_url = "http://absolute/" + n_url;
|
|
}
|
|
|
|
if (n_is_local == is_local && n_fps_custom == fps_custom && n_fps == fps &&
|
|
n_shutdown == shutdown_on_invisible && n_restart == restart && n_css == css && n_url == url &&
|
|
n_reroute == reroute_audio && n_webpage_control_level == webpage_control_level) {
|
|
|
|
if (n_width == width && n_height == height)
|
|
return;
|
|
|
|
width = n_width;
|
|
height = n_height;
|
|
ExecuteOnBrowser(
|
|
[this](CefRefPtr<CefBrowser> cefBrowser) {
|
|
const CefSize cefSize(width, height);
|
|
cefBrowser->GetHost()->GetClient()->GetDisplayHandler()->OnAutoResize(
|
|
cefBrowser, cefSize);
|
|
cefBrowser->GetHost()->WasResized();
|
|
cefBrowser->GetHost()->Invalidate(PET_VIEW);
|
|
},
|
|
true);
|
|
return;
|
|
}
|
|
|
|
is_local = n_is_local;
|
|
width = n_width;
|
|
height = n_height;
|
|
fps = n_fps;
|
|
fps_custom = n_fps_custom;
|
|
shutdown_on_invisible = n_shutdown;
|
|
reroute_audio = n_reroute;
|
|
webpage_control_level = n_webpage_control_level;
|
|
restart = n_restart;
|
|
css = n_css;
|
|
url = n_url;
|
|
|
|
obs_source_set_audio_active(source, reroute_audio);
|
|
}
|
|
|
|
DestroyBrowser();
|
|
DestroyTextures();
|
|
|
|
if (!shutdown_on_invisible || obs_source_showing(source))
|
|
create_browser = true;
|
|
|
|
first_update = false;
|
|
}
|
|
|
|
void BrowserSource::Tick()
|
|
{
|
|
if (os_event_try(cef_started_event) != 0)
|
|
return;
|
|
|
|
if (create_browser && CreateBrowser())
|
|
create_browser = false;
|
|
#if defined(ENABLE_BROWSER_SHARED_TEXTURE)
|
|
#if defined(BROWSER_EXTERNAL_BEGIN_FRAME_ENABLED)
|
|
if (!fps_custom)
|
|
reset_frame = true;
|
|
#else
|
|
struct obs_video_info ovi;
|
|
obs_get_video_info(&ovi);
|
|
double video_fps = (double)ovi.fps_num / (double)ovi.fps_den;
|
|
|
|
if (!fps_custom) {
|
|
if (!!cefBrowser && canvas_fps != video_fps) {
|
|
cefBrowser->GetHost()->SetWindowlessFrameRate(video_fps);
|
|
canvas_fps = video_fps;
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
extern void ProcessCef();
|
|
|
|
void BrowserSource::Render()
|
|
{
|
|
bool flip = false;
|
|
#if defined(ENABLE_BROWSER_SHARED_TEXTURE) && CHROME_VERSION_BUILD < 6367
|
|
flip = hwaccel;
|
|
#endif
|
|
|
|
if (texture) {
|
|
#ifdef __APPLE__
|
|
int type = gs_get_device_type();
|
|
gs_effect_t *effect;
|
|
|
|
if (type == GS_DEVICE_OPENGL) {
|
|
effect = obs_get_base_effect((hwaccel) ? OBS_EFFECT_DEFAULT_RECT : OBS_EFFECT_DEFAULT);
|
|
} else {
|
|
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
|
}
|
|
#else
|
|
gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
|
#endif
|
|
|
|
bool linear_sample = extra_texture == NULL;
|
|
gs_texture_t *draw_texture = texture;
|
|
if (!linear_sample && !obs_source_get_texcoords_centered(source)) {
|
|
gs_copy_texture(extra_texture, texture);
|
|
draw_texture = extra_texture;
|
|
|
|
linear_sample = true;
|
|
}
|
|
|
|
const bool previous = gs_framebuffer_srgb_enabled();
|
|
gs_enable_framebuffer_srgb(true);
|
|
|
|
gs_blend_state_push();
|
|
gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
|
|
|
|
gs_eparam_t *const image = gs_effect_get_param_by_name(effect, "image");
|
|
|
|
const char *tech;
|
|
if (linear_sample) {
|
|
gs_effect_set_texture_srgb(image, draw_texture);
|
|
tech = "Draw";
|
|
} else {
|
|
gs_effect_set_texture(image, draw_texture);
|
|
tech = "DrawSrgbDecompress";
|
|
}
|
|
|
|
const uint32_t flip_flag = flip ? GS_FLIP_V : 0;
|
|
while (gs_effect_loop(effect, tech))
|
|
gs_draw_sprite(draw_texture, flip_flag, 0, 0);
|
|
|
|
gs_blend_state_pop();
|
|
|
|
gs_enable_framebuffer_srgb(previous);
|
|
}
|
|
|
|
#if defined(BROWSER_EXTERNAL_BEGIN_FRAME_ENABLED) && defined(ENABLE_BROWSER_SHARED_TEXTURE)
|
|
SignalBeginFrame();
|
|
#elif defined(ENABLE_BROWSER_QT_LOOP)
|
|
ProcessCef();
|
|
#endif
|
|
}
|
|
|
|
static void ExecuteOnBrowser(BrowserFunc func, BrowserSource *bs)
|
|
{
|
|
lock_guard<mutex> lock(browser_list_mutex);
|
|
|
|
if (bs) {
|
|
BrowserSource *bsw = reinterpret_cast<BrowserSource *>(bs);
|
|
bsw->ExecuteOnBrowser(func, true);
|
|
}
|
|
}
|
|
|
|
static void ExecuteOnAllBrowsers(BrowserFunc func)
|
|
{
|
|
lock_guard<mutex> lock(browser_list_mutex);
|
|
|
|
BrowserSource *bs = first_browser;
|
|
while (bs) {
|
|
BrowserSource *bsw = reinterpret_cast<BrowserSource *>(bs);
|
|
bsw->ExecuteOnBrowser(func, true);
|
|
bs = bs->next;
|
|
}
|
|
}
|
|
|
|
void DispatchJSEvent(std::string eventName, std::string jsonString, BrowserSource *browser)
|
|
{
|
|
const auto jsEvent = [eventName, jsonString](CefRefPtr<CefBrowser> cefBrowser) {
|
|
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("DispatchJSEvent");
|
|
CefRefPtr<CefListValue> args = msg->GetArgumentList();
|
|
|
|
args->SetString(0, eventName);
|
|
args->SetString(1, jsonString);
|
|
SendBrowserProcessMessage(cefBrowser, PID_RENDERER, msg);
|
|
};
|
|
|
|
if (!browser)
|
|
ExecuteOnAllBrowsers(jsEvent);
|
|
else
|
|
ExecuteOnBrowser(jsEvent, browser);
|
|
}
|