window-projector.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. #include <QAction>
  2. #include <QGuiApplication>
  3. #include <QMouseEvent>
  4. #include <QMenu>
  5. #include <QScreen>
  6. #include "window-projector.hpp"
  7. #include "display-helpers.hpp"
  8. #include "qt-wrappers.hpp"
  9. #include "platform.hpp"
  10. static QList<OBSProjector *> multiviewProjectors;
  11. static bool updatingMultiview = false;
  12. OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, bool window)
  13. : OBSQTDisplay (widget,
  14. Qt::Window),
  15. source (source_),
  16. removedSignal (obs_source_get_signal_handler(source),
  17. "remove", OBSSourceRemoved, this)
  18. {
  19. if (!window) {
  20. setWindowFlags(Qt::FramelessWindowHint |
  21. Qt::X11BypassWindowManagerHint);
  22. }
  23. setAttribute(Qt::WA_DeleteOnClose, true);
  24. //disable application quit when last window closed
  25. setAttribute(Qt::WA_QuitOnClose, false);
  26. installEventFilter(CreateShortcutFilter());
  27. auto addDrawCallback = [this] ()
  28. {
  29. bool isMultiview = type == ProjectorType::Multiview;
  30. obs_display_add_draw_callback(GetDisplay(),
  31. isMultiview ? OBSRenderMultiview : OBSRender,
  32. this);
  33. obs_display_set_background_color(GetDisplay(), 0x000000);
  34. };
  35. connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback);
  36. bool hideCursor = config_get_bool(GetGlobalConfig(),
  37. "BasicWindow", "HideProjectorCursor");
  38. if (hideCursor && !window) {
  39. QPixmap empty(16, 16);
  40. empty.fill(Qt::transparent);
  41. setCursor(QCursor(empty));
  42. }
  43. App()->IncrementSleepInhibition();
  44. resize(480, 270);
  45. }
  46. OBSProjector::~OBSProjector()
  47. {
  48. bool isMultiview = type == ProjectorType::Multiview;
  49. obs_display_remove_draw_callback(GetDisplay(),
  50. isMultiview ? OBSRenderMultiview : OBSRender, this);
  51. if (source)
  52. obs_source_dec_showing(source);
  53. if (isMultiview) {
  54. for (OBSWeakSource &weakSrc : multiviewScenes) {
  55. OBSSource src = OBSGetStrongRef(weakSrc);
  56. if (src)
  57. obs_source_dec_showing(src);
  58. }
  59. obs_enter_graphics();
  60. gs_vertexbuffer_destroy(outerBox);
  61. gs_vertexbuffer_destroy(innerBox);
  62. gs_vertexbuffer_destroy(leftVLine);
  63. gs_vertexbuffer_destroy(rightVLine);
  64. gs_vertexbuffer_destroy(leftLine);
  65. gs_vertexbuffer_destroy(topLine);
  66. gs_vertexbuffer_destroy(rightLine);
  67. obs_leave_graphics();
  68. }
  69. if (type == ProjectorType::Multiview)
  70. multiviewProjectors.removeAll(this);
  71. App()->DecrementSleepInhibition();
  72. }
  73. static OBSSource CreateLabel(const char *name, size_t h)
  74. {
  75. obs_data_t *settings = obs_data_create();
  76. obs_data_t *font = obs_data_create();
  77. std::string text;
  78. text += " ";
  79. text += name;
  80. text += " ";
  81. #if defined(_WIN32)
  82. obs_data_set_string(font, "face", "Arial");
  83. #elif defined(__APPLE__)
  84. obs_data_set_string(font, "face", "Helvetica");
  85. #else
  86. obs_data_set_string(font, "face", "Monospace");
  87. #endif
  88. obs_data_set_int(font, "flags", 0);
  89. obs_data_set_int(font, "size", int(h / 9.81));
  90. obs_data_set_obj(settings, "font", font);
  91. obs_data_set_string(settings, "text", text.c_str());
  92. obs_data_set_bool(settings, "outline", true);
  93. OBSSource txtSource = obs_source_create_private("text_ft2_source", name,
  94. settings);
  95. obs_source_release(txtSource);
  96. obs_data_release(font);
  97. obs_data_release(settings);
  98. return txtSource;
  99. }
  100. void OBSProjector::Init(int monitor, bool window, QString title,
  101. ProjectorType type_)
  102. {
  103. QScreen *screen = QGuiApplication::screens()[monitor];
  104. if (!window)
  105. setGeometry(screen->geometry());
  106. bool alwaysOnTop = config_get_bool(GetGlobalConfig(),
  107. "BasicWindow", "ProjectorAlwaysOnTop");
  108. if (alwaysOnTop && !window)
  109. SetAlwaysOnTop(this, true);
  110. if (window)
  111. setWindowTitle(title);
  112. show();
  113. if (source)
  114. obs_source_inc_showing(source);
  115. if (!window) {
  116. QAction *action = new QAction(this);
  117. action->setShortcut(Qt::Key_Escape);
  118. addAction(action);
  119. connect(action, SIGNAL(triggered()), this,
  120. SLOT(EscapeTriggered()));
  121. activateWindow();
  122. }
  123. savedMonitor = monitor;
  124. isWindow = window;
  125. type = type_;
  126. if (type == ProjectorType::Multiview) {
  127. obs_enter_graphics();
  128. gs_render_start(true);
  129. gs_vertex2f(0.001f, 0.001f);
  130. gs_vertex2f(0.001f, 0.997f);
  131. gs_vertex2f(0.997f, 0.997f);
  132. gs_vertex2f(0.997f, 0.001f);
  133. gs_vertex2f(0.001f, 0.001f);
  134. outerBox = gs_render_save();
  135. gs_render_start(true);
  136. gs_vertex2f(0.04f, 0.04f);
  137. gs_vertex2f(0.04f, 0.96f);
  138. gs_vertex2f(0.96f, 0.96f);
  139. gs_vertex2f(0.96f, 0.04f);
  140. gs_vertex2f(0.04f, 0.04f);
  141. innerBox = gs_render_save();
  142. gs_render_start(true);
  143. gs_vertex2f(0.15f, 0.04f);
  144. gs_vertex2f(0.15f, 0.96f);
  145. leftVLine = gs_render_save();
  146. gs_render_start(true);
  147. gs_vertex2f(0.85f, 0.04f);
  148. gs_vertex2f(0.85f, 0.96f);
  149. rightVLine = gs_render_save();
  150. gs_render_start(true);
  151. gs_vertex2f(0.0f, 0.5f);
  152. gs_vertex2f(0.075f, 0.5f);
  153. leftLine = gs_render_save();
  154. gs_render_start(true);
  155. gs_vertex2f(0.5f, 0.0f);
  156. gs_vertex2f(0.5f, 0.09f);
  157. topLine = gs_render_save();
  158. gs_render_start(true);
  159. gs_vertex2f(0.925f, 0.5f);
  160. gs_vertex2f(1.0f, 0.5f);
  161. rightLine = gs_render_save();
  162. obs_leave_graphics();
  163. UpdateMultiview();
  164. multiviewProjectors.push_back(this);
  165. }
  166. ready = true;
  167. }
  168. static inline void renderVB(gs_effect_t *effect, gs_vertbuffer_t *vb,
  169. int cx, int cy)
  170. {
  171. if (!vb)
  172. return;
  173. matrix4 transform;
  174. matrix4_identity(&transform);
  175. transform.x.x = cx;
  176. transform.y.y = cy;
  177. gs_load_vertexbuffer(vb);
  178. gs_matrix_push();
  179. gs_matrix_mul(&transform);
  180. while (gs_effect_loop(effect, "Solid"))
  181. gs_draw(GS_LINESTRIP, 0, 0);
  182. gs_matrix_pop();
  183. }
  184. static inline uint32_t labelOffset(obs_source_t *label, uint32_t cx)
  185. {
  186. uint32_t w = obs_source_get_width(label);
  187. w = uint32_t(float(w) * 0.5f);
  188. return (cx / 2) - w;
  189. }
  190. void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
  191. {
  192. OBSProjector *window = (OBSProjector *)data;
  193. if (updatingMultiview || !window->ready)
  194. return;
  195. OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
  196. uint32_t targetCX, targetCY;
  197. int x, y;
  198. float fX, fY, halfCX, halfCY, sourceX, sourceY,
  199. quarterCX, quarterCY, scale, targetCXF, targetCYF,
  200. hiCX, hiCY, qiX, qiY, qiCX, qiCY,
  201. hiScaleX, hiScaleY, qiScaleX, qiScaleY;
  202. uint32_t offset;
  203. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  204. gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
  205. struct obs_video_info ovi;
  206. obs_get_video_info(&ovi);
  207. targetCX = ovi.base_width;
  208. targetCY = ovi.base_height;
  209. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  210. targetCXF = float(targetCX);
  211. targetCYF = float(targetCY);
  212. fX = float(x);
  213. fY = float(y);
  214. halfCX = (targetCXF + 1) / 2;
  215. halfCY = (targetCYF + 1) / 2;
  216. hiCX = (halfCX - 4.0);
  217. hiCY = (halfCY - 4.0);
  218. hiScaleX = hiCX / targetCXF;
  219. hiScaleY = hiCY / targetCYF;
  220. quarterCX = (halfCX + 1) / 2;
  221. quarterCY = (halfCY + 1) / 2;
  222. qiCX = (quarterCX - 8.0);
  223. qiCY = (quarterCY - 8.0);
  224. qiScaleX = qiCX / targetCXF;
  225. qiScaleY = qiCY / targetCYF;
  226. OBSSource previewSrc = main->GetCurrentSceneSource();
  227. OBSSource programSrc = main->GetProgramSource();
  228. bool studioMode = main->IsPreviewProgramMode();
  229. auto drawBox = [solid, color] (float cx, float cy,
  230. uint32_t colorVal)
  231. {
  232. gs_effect_set_color(color, colorVal);
  233. while (gs_effect_loop(solid, "Solid"))
  234. gs_draw_sprite(nullptr, 0, (uint32_t)cx, (uint32_t)cy);
  235. };
  236. auto setRegion = [fX, fY, scale] (float x, float y, float cx, float cy)
  237. {
  238. float vX = int(fX + x * scale);
  239. float vY = int(fY + y * scale);
  240. float vCX = int(cx * scale);
  241. float vCY = int(cy * scale);
  242. float oL = x;
  243. float oT = y;
  244. float oR = (x + cx);
  245. float oB = (y + cy);
  246. gs_projection_push();
  247. gs_viewport_push();
  248. gs_set_viewport(vX, vY, vCX, vCY);
  249. gs_ortho(oL, oR, oT, oB, -100.0f, 100.0f);
  250. };
  251. auto resetRegion = [] ()
  252. {
  253. gs_viewport_pop();
  254. gs_projection_pop();
  255. };
  256. /* ----------------------------- */
  257. /* draw sources */
  258. gs_projection_push();
  259. gs_viewport_push();
  260. gs_set_viewport(x, y, targetCX * scale, targetCY * scale);
  261. gs_ortho(0.0f, targetCXF, 0.0f, targetCYF, -100.0f, 100.0f);
  262. for (size_t i = 0; i < 8; i++) {
  263. OBSSource src = OBSGetStrongRef(window->multiviewScenes[i]);
  264. obs_source *label = window->multiviewLabels[i + 2];
  265. if (!src)
  266. continue;
  267. if (!label)
  268. continue;
  269. if (i < 4) {
  270. sourceX = (float(i) * quarterCX);
  271. sourceY = halfCY;
  272. } else {
  273. sourceX = (float(i - 4) * quarterCX);
  274. sourceY = halfCY + quarterCY;
  275. }
  276. qiX = sourceX + 4.0f;
  277. qiY = sourceY + 4.0f;
  278. /* ----------- */
  279. if (src == previewSrc || src == programSrc) {
  280. uint32_t colorVal = src == programSrc
  281. ? 0xFFFF0000
  282. : 0xFF00FF00;
  283. gs_matrix_push();
  284. gs_matrix_translate3f(sourceX, sourceY, 0.0f);
  285. drawBox(quarterCX, quarterCY, colorVal);
  286. gs_matrix_pop();
  287. gs_matrix_push();
  288. gs_matrix_translate3f(qiX, qiY, 0.0f);
  289. drawBox(qiCX, qiCY, 0xFF000000);
  290. gs_matrix_pop();
  291. }
  292. /* ----------- */
  293. gs_matrix_push();
  294. gs_matrix_translate3f(qiX, qiY, 0.0f);
  295. gs_matrix_scale3f(qiScaleX, qiScaleY, 1.0f);
  296. setRegion(qiX, qiY, qiCX, qiCY);
  297. obs_source_video_render(src);
  298. resetRegion();
  299. gs_effect_set_color(color, 0xFFFFFFFF);
  300. renderVB(solid, window->outerBox, targetCX, targetCY);
  301. gs_matrix_pop();
  302. /* ----------- */
  303. offset = labelOffset(label, quarterCX);
  304. cx = obs_source_get_width(label);
  305. cy = obs_source_get_height(label);
  306. gs_matrix_push();
  307. gs_matrix_translate3f(sourceX + offset,
  308. (quarterCY * 0.8f) + sourceY, 0.0f);
  309. drawBox(cx, cy + int(quarterCX * 0.015f), 0xD91F1F1F);
  310. obs_source_video_render(label);
  311. gs_matrix_pop();
  312. }
  313. gs_effect_set_color(color, 0xFFFFFFFF);
  314. /* ----------------------------- */
  315. /* draw preview */
  316. gs_matrix_push();
  317. gs_matrix_translate3f(2.0f, 2.0f, 0.0f);
  318. gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
  319. setRegion(2.0f, 2.0f, hiCX, hiCY);
  320. if (studioMode) {
  321. obs_source_video_render(previewSrc);
  322. } else {
  323. obs_render_main_texture();
  324. }
  325. resetRegion();
  326. gs_matrix_pop();
  327. /* ----------- */
  328. gs_matrix_push();
  329. gs_matrix_scale3f(0.5f, 0.5f, 1.0f);
  330. renderVB(solid, window->outerBox, targetCX, targetCY);
  331. renderVB(solid, window->innerBox, targetCX, targetCY);
  332. renderVB(solid, window->leftVLine, targetCX, targetCY);
  333. renderVB(solid, window->rightVLine, targetCX, targetCY);
  334. renderVB(solid, window->leftLine, targetCX, targetCY);
  335. renderVB(solid, window->topLine, targetCX, targetCY);
  336. renderVB(solid, window->rightLine, targetCX, targetCY);
  337. gs_matrix_pop();
  338. /* ----------- */
  339. obs_source_t *previewLabel = window->multiviewLabels[0];
  340. offset = labelOffset(previewLabel, halfCX);
  341. cx = obs_source_get_width(previewLabel);
  342. cy = obs_source_get_height(previewLabel);
  343. gs_matrix_push();
  344. gs_matrix_translate3f(offset, (halfCY * 0.8f), 0.0f);
  345. drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F);
  346. obs_source_video_render(previewLabel);
  347. gs_matrix_pop();
  348. /* ----------------------------- */
  349. /* draw program */
  350. gs_matrix_push();
  351. gs_matrix_translate3f(halfCX + 2.0, 2.0f, 0.0f);
  352. gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
  353. setRegion(halfCX + 2.0f, 2.0f, hiCX, hiCY);
  354. obs_render_main_texture();
  355. resetRegion();
  356. gs_matrix_pop();
  357. /* ----------- */
  358. gs_matrix_push();
  359. gs_matrix_translate3f(halfCX, 0.0f, 0.0f);
  360. gs_matrix_scale3f(0.5f, 0.5f, 1.0f);
  361. renderVB(solid, window->outerBox, targetCX, targetCY);
  362. gs_matrix_pop();
  363. /* ----------- */
  364. obs_source_t *programLabel = window->multiviewLabels[1];
  365. offset = labelOffset(programLabel, halfCX);
  366. cx = obs_source_get_width(programLabel);
  367. cy = obs_source_get_height(programLabel);
  368. gs_matrix_push();
  369. gs_matrix_translate3f(halfCX + offset, (halfCY * 0.8f), 0.0f);
  370. drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F);
  371. obs_source_video_render(programLabel);
  372. gs_matrix_pop();
  373. /* ----------------------------- */
  374. gs_viewport_pop();
  375. gs_projection_pop();
  376. }
  377. void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
  378. {
  379. OBSProjector *window = reinterpret_cast<OBSProjector*>(data);
  380. if (!window->ready)
  381. return;
  382. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  383. uint32_t targetCX;
  384. uint32_t targetCY;
  385. int x, y;
  386. int newCX, newCY;
  387. float scale;
  388. if (window->source) {
  389. targetCX = std::max(obs_source_get_width(window->source), 1u);
  390. targetCY = std::max(obs_source_get_height(window->source), 1u);
  391. } else {
  392. struct obs_video_info ovi;
  393. obs_get_video_info(&ovi);
  394. targetCX = ovi.base_width;
  395. targetCY = ovi.base_height;
  396. }
  397. GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
  398. newCX = int(scale * float(targetCX));
  399. newCY = int(scale * float(targetCY));
  400. gs_viewport_push();
  401. gs_projection_push();
  402. gs_ortho(0.0f, float(targetCX), 0.0f, float(targetCY), -100.0f, 100.0f);
  403. gs_set_viewport(x, y, newCX, newCY);
  404. OBSSource source = window->source;
  405. if (window->type == ProjectorType::Preview &&
  406. main->IsPreviewProgramMode()) {
  407. OBSSource curSource = main->GetCurrentSceneSource();
  408. if (window->source != curSource) {
  409. obs_source_dec_showing(window->source);
  410. obs_source_inc_showing(curSource);
  411. source = curSource;
  412. }
  413. }
  414. if (source) {
  415. obs_source_video_render(source);
  416. } else {
  417. obs_render_main_texture();
  418. }
  419. gs_projection_pop();
  420. gs_viewport_pop();
  421. }
  422. void OBSProjector::OBSSourceRemoved(void *data, calldata_t *params)
  423. {
  424. OBSProjector *window = reinterpret_cast<OBSProjector*>(data);
  425. window->deleteLater();
  426. UNUSED_PARAMETER(params);
  427. }
  428. void OBSProjector::mousePressEvent(QMouseEvent *event)
  429. {
  430. OBSQTDisplay::mousePressEvent(event);
  431. if (event->button() == Qt::RightButton) {
  432. QMenu popup(this);
  433. popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered()));
  434. popup.exec(QCursor::pos());
  435. }
  436. }
  437. void OBSProjector::EscapeTriggered()
  438. {
  439. if (!isWindow) {
  440. OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window();
  441. main->RemoveSavedProjectors(savedMonitor);
  442. }
  443. deleteLater();
  444. }
  445. void OBSProjector::UpdateMultiview()
  446. {
  447. for (OBSWeakSource &val : multiviewScenes)
  448. val = nullptr;
  449. for (OBSSource &val : multiviewLabels)
  450. val = nullptr;
  451. struct obs_video_info ovi;
  452. obs_get_video_info(&ovi);
  453. uint32_t h = ovi.base_height;
  454. struct obs_frontend_source_list scenes = {};
  455. obs_frontend_get_scenes(&scenes);
  456. int curIdx = 0;
  457. multiviewLabels[0] = CreateLabel(Str("StudioMode.Preview"), h / 2);
  458. multiviewLabels[1] = CreateLabel(Str("StudioMode.Program"), h / 2);
  459. for (size_t i = 0; i < scenes.sources.num && curIdx < 8; i++) {
  460. obs_source_t *src = scenes.sources.array[i];
  461. OBSData data = obs_source_get_private_settings(src);
  462. obs_data_release(data);
  463. obs_data_set_default_bool(data, "show_in_multiview", true);
  464. if (!obs_data_get_bool(data, "show_in_multiview"))
  465. continue;
  466. multiviewScenes[curIdx] = OBSGetWeakRef(src);
  467. obs_source_inc_showing(src);
  468. std::string name;
  469. name += std::to_string(curIdx + 1);
  470. name += " - ";
  471. name += obs_source_get_name(src);
  472. if (name.size() > 15)
  473. name.resize(15);
  474. multiviewLabels[curIdx + 2] = CreateLabel(name.c_str(), h / 4);
  475. curIdx++;
  476. }
  477. obs_frontend_source_list_free(&scenes);
  478. }
  479. void OBSProjector::UpdateMultiviewProjectors()
  480. {
  481. obs_enter_graphics();
  482. updatingMultiview = true;
  483. obs_leave_graphics();
  484. for (auto &projector : multiviewProjectors)
  485. projector->UpdateMultiview();
  486. obs_enter_graphics();
  487. updatingMultiview = false;
  488. obs_leave_graphics();
  489. }