diff --git a/UI/display-helpers.hpp b/UI/display-helpers.hpp index eff3c51ea..42f813de1 100644 --- a/UI/display-helpers.hpp +++ b/UI/display-helpers.hpp @@ -17,6 +17,9 @@ #pragma once +#include +#include + static inline void GetScaleAndCenterPos(int baseCX, int baseCY, int windowCX, int windowCY, int &x, int &y, float &scale) @@ -53,3 +56,90 @@ static inline QSize GetPixelSize(QWidget *widget) { return widget->size() * widget->devicePixelRatioF(); } + +#define OUTLINE_COLOR 0xFFD0D0D0 +#define LINE_LENGTH 0.1f + +// Rec. ITU-R BT.1848-1 / EBU R 95 +#define ACTION_SAFE_PERCENT 0.035f // 3.5% +#define GRAPHICS_SAFE_PERCENT 0.05f // 5.0% +#define FOURBYTHREE_SAFE_PERCENT 0.1625f // 16.25% + +static inline void InitSafeAreas(gs_vertbuffer_t **actionSafeMargin, + gs_vertbuffer_t **graphicsSafeMargin, + gs_vertbuffer_t **fourByThreeSafeMargin, + gs_vertbuffer_t **leftLine, + gs_vertbuffer_t **topLine, + gs_vertbuffer_t **rightLine) +{ + obs_enter_graphics(); + + // All essential action should be placed inside this area + gs_render_start(true); + gs_vertex2f(ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT); + gs_vertex2f(ACTION_SAFE_PERCENT, 1 - ACTION_SAFE_PERCENT); + gs_vertex2f(1 - ACTION_SAFE_PERCENT, 1 - ACTION_SAFE_PERCENT); + gs_vertex2f(1 - ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT); + gs_vertex2f(ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT); + *actionSafeMargin = gs_render_save(); + + // All graphics should be placed inside this area + gs_render_start(true); + gs_vertex2f(GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT); + gs_vertex2f(GRAPHICS_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT); + gs_vertex2f(1 - GRAPHICS_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT); + gs_vertex2f(1 - GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT); + gs_vertex2f(GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT); + *graphicsSafeMargin = gs_render_save(); + + // 4:3 safe area for widescreen + gs_render_start(true); + gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT); + gs_vertex2f(1 - FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT); + gs_vertex2f(1 - FOURBYTHREE_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT); + gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT); + gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT); + *fourByThreeSafeMargin = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.0f, 0.5f); + gs_vertex2f(LINE_LENGTH, 0.5f); + *leftLine = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(0.5f, 0.0f); + gs_vertex2f(0.5f, LINE_LENGTH); + *topLine = gs_render_save(); + + gs_render_start(true); + gs_vertex2f(1.0f, 0.5f); + gs_vertex2f(1 - LINE_LENGTH, 0.5f); + *rightLine = gs_render_save(); + + obs_leave_graphics(); +} + +static inline void RenderSafeAreas(gs_vertbuffer_t *vb, int cx, int cy) +{ + if (!vb) + return; + + matrix4 transform; + matrix4_identity(&transform); + transform.x.x = cx; + transform.y.y = cy; + + gs_load_vertexbuffer(vb); + + gs_matrix_push(); + gs_matrix_mul(&transform); + + gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); + gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); + + gs_effect_set_color(color, OUTLINE_COLOR); + while (gs_effect_loop(solid, "Solid")) + gs_draw(GS_LINESTRIP, 0, 0); + + gs_matrix_pop(); +} diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index 99d73d08d..794b17a46 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -595,6 +595,13 @@ + + + + Basic.Settings.General.Multiview.DrawSafeAreas + + + diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 89b051a28..1cf42f60f 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -458,6 +458,8 @@ OBSBasic::OBSBasic(QWidget *parent) connect(ui->broadcastButton, &QPushButton::clicked, this, &OBSBasic::BroadcastButtonClicked); + + UpdatePreviewSafeAreas(); } static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, @@ -1642,6 +1644,8 @@ void OBSBasic::InitPrimitives() } circle = gs_render_save(); + InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, + &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); obs_leave_graphics(); } @@ -2643,6 +2647,12 @@ OBSBasic::~OBSBasic() gs_vertexbuffer_destroy(boxRight); gs_vertexbuffer_destroy(boxBottom); gs_vertexbuffer_destroy(circle); + gs_vertexbuffer_destroy(actionSafeMargin); + gs_vertexbuffer_destroy(graphicsSafeMargin); + gs_vertexbuffer_destroy(fourByThreeSafeMargin); + gs_vertexbuffer_destroy(leftLine); + gs_vertexbuffer_destroy(topLine); + gs_vertexbuffer_destroy(rightLine); obs_leave_graphics(); /* When shutting down, sometimes source references can get in to the @@ -4081,6 +4091,19 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) window->ui->preview->DrawSceneEditing(); + uint32_t targetCX = window->previewCX; + uint32_t targetCY = window->previewCY; + + if (window->drawSafeAreas) { + RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); + RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); + RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, + targetCY); + RenderSafeAreas(window->leftLine, targetCX, targetCY); + RenderSafeAreas(window->topLine, targetCX, targetCY); + RenderSafeAreas(window->rightLine, targetCX, targetCY); + } + /* --------------------------------------- */ gs_projection_pop(); @@ -9650,3 +9673,9 @@ void OBSBasic::ShowStatusBarMessage(const QString &message) ui->statusbar->clearMessage(); ui->statusbar->showMessage(message, 10000); } + +void OBSBasic::UpdatePreviewSafeAreas() +{ + drawSafeAreas = config_get_bool(App()->GlobalConfig(), "BasicWindow", + "ShowSafeAreas"); +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index a52e30117..7d5ec2033 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -243,6 +243,13 @@ private: gs_vertbuffer_t *boxBottom = nullptr; gs_vertbuffer_t *circle = nullptr; + gs_vertbuffer_t *actionSafeMargin = nullptr; + gs_vertbuffer_t *graphicsSafeMargin = nullptr; + gs_vertbuffer_t *fourByThreeSafeMargin = nullptr; + gs_vertbuffer_t *leftLine = nullptr; + gs_vertbuffer_t *topLine = nullptr; + gs_vertbuffer_t *rightLine = nullptr; + int previewX = 0, previewY = 0; int previewCX = 0, previewCY = 0; float previewScale = 0.0f; @@ -570,6 +577,9 @@ private: #endif void BroadcastButtonClicked(); + void UpdatePreviewSafeAreas(); + bool drawSafeAreas = false; + public slots: void DeferSaveBegin(); void DeferSaveEnd(); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index f4d2adae3..e41d7bbf5 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -408,6 +408,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->overflowHide, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->overflowAlwaysVisible,CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->overflowSelectionHide,CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->previewSafeAreas, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->automaticSearch, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->doubleClickSwitch, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->studioPortraitLayout, CHECK_CHANGED, GENERAL_CHANGED); @@ -1313,6 +1314,10 @@ void OBSBasicSettings::LoadGeneralSettings() GetGlobalConfig(), "BasicWindow", "OverflowSelectionHidden"); ui->overflowSelectionHide->setChecked(overflowSelectionHide); + bool safeAreas = config_get_bool(GetGlobalConfig(), "BasicWindow", + "ShowSafeAreas"); + ui->previewSafeAreas->setChecked(safeAreas); + bool automaticSearch = config_get_bool(GetGlobalConfig(), "General", "AutomaticCollectionSearch"); ui->automaticSearch->setChecked(automaticSearch); @@ -3021,6 +3026,12 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(GetGlobalConfig(), "BasicWindow", "OverflowSelectionHidden", ui->overflowSelectionHide->isChecked()); + if (WidgetChanged(ui->previewSafeAreas)) { + config_set_bool(GetGlobalConfig(), "BasicWindow", + "ShowSafeAreas", + ui->previewSafeAreas->isChecked()); + main->UpdatePreviewSafeAreas(); + } if (WidgetChanged(ui->doubleClickSwitch)) config_set_bool(GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick", diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp index e7a25b7ef..3dac181b3 100644 --- a/UI/window-projector.cpp +++ b/UI/window-projector.cpp @@ -70,58 +70,9 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, int monitor, &OBSProjector::ScreenRemoved); if (type == ProjectorType::Multiview) { - obs_enter_graphics(); - - // All essential action should be placed inside this area - gs_render_start(true); - gs_vertex2f(actionSafePercentage, actionSafePercentage); - gs_vertex2f(actionSafePercentage, 1 - actionSafePercentage); - gs_vertex2f(1 - actionSafePercentage, 1 - actionSafePercentage); - gs_vertex2f(1 - actionSafePercentage, actionSafePercentage); - gs_vertex2f(actionSafePercentage, actionSafePercentage); - actionSafeMargin = gs_render_save(); - - // All graphics should be placed inside this area - gs_render_start(true); - gs_vertex2f(graphicsSafePercentage, graphicsSafePercentage); - gs_vertex2f(graphicsSafePercentage, 1 - graphicsSafePercentage); - gs_vertex2f(1 - graphicsSafePercentage, - 1 - graphicsSafePercentage); - gs_vertex2f(1 - graphicsSafePercentage, graphicsSafePercentage); - gs_vertex2f(graphicsSafePercentage, graphicsSafePercentage); - graphicsSafeMargin = gs_render_save(); - - // 4:3 safe area for widescreen - gs_render_start(true); - gs_vertex2f(fourByThreeSafePercentage, graphicsSafePercentage); - gs_vertex2f(1 - fourByThreeSafePercentage, - graphicsSafePercentage); - gs_vertex2f(1 - fourByThreeSafePercentage, - 1 - graphicsSafePercentage); - gs_vertex2f(fourByThreeSafePercentage, - 1 - graphicsSafePercentage); - gs_vertex2f(fourByThreeSafePercentage, graphicsSafePercentage); - fourByThreeSafeMargin = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.5f); - gs_vertex2f(lineLength, 0.5f); - leftLine = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.5f, 0.0f); - gs_vertex2f(0.5f, lineLength); - topLine = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.5f); - gs_vertex2f(1 - lineLength, 0.5f); - rightLine = gs_render_save(); - obs_leave_graphics(); - - solid = obs_get_base_effect(OBS_EFFECT_SOLID); - color = gs_effect_get_param_by_name(solid, "color"); - + InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, + &fourByThreeSafeMargin, &leftLine, &topLine, + &rightLine); UpdateMultiview(); multiviewProjectors.push_back(this); @@ -297,31 +248,13 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy) OBSSource programSrc = main->GetProgramSource(); bool studioMode = main->IsPreviewProgramMode(); - auto renderVB = [&](gs_vertbuffer_t *vb, int cx, int cy, - uint32_t colorVal) { - if (!vb) - return; - - matrix4 transform; - matrix4_identity(&transform); - transform.x.x = cx; - transform.y.y = cy; - - gs_load_vertexbuffer(vb); - - gs_matrix_push(); - gs_matrix_mul(&transform); - - gs_effect_set_color(window->color, colorVal); - while (gs_effect_loop(window->solid, "Solid")) - gs_draw(GS_LINESTRIP, 0, 0); - - gs_matrix_pop(); - }; - auto drawBox = [&](float cx, float cy, uint32_t colorVal) { - gs_effect_set_color(window->color, colorVal); - while (gs_effect_loop(window->solid, "Solid")) + gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); + gs_eparam_t *color = + gs_effect_get_param_by_name(solid, "color"); + + gs_effect_set_color(color, colorVal); + while (gs_effect_loop(solid, "Solid")) gs_draw_sprite(nullptr, 0, (uint32_t)cx, (uint32_t)cy); }; @@ -549,17 +482,17 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy) obs_source_video_render(previewSrc); else obs_render_main_texture(); + if (drawSafeArea) { - renderVB(window->actionSafeMargin, targetCX, targetCY, - outerColor); - renderVB(window->graphicsSafeMargin, targetCX, targetCY, - outerColor); - renderVB(window->fourByThreeSafeMargin, targetCX, targetCY, - outerColor); - renderVB(window->leftLine, targetCX, targetCY, outerColor); - renderVB(window->topLine, targetCX, targetCY, outerColor); - renderVB(window->rightLine, targetCX, targetCY, outerColor); + RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); + RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); + RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, + targetCY); + RenderSafeAreas(window->leftLine, targetCX, targetCY); + RenderSafeAreas(window->topLine, targetCX, targetCY); + RenderSafeAreas(window->rightLine, targetCX, targetCY); } + endRegion(); gs_matrix_pop(); diff --git a/UI/window-projector.hpp b/UI/window-projector.hpp index 7080e4f39..ab09265b7 100644 --- a/UI/window-projector.hpp +++ b/UI/window-projector.hpp @@ -49,8 +49,6 @@ private: gs_vertbuffer_t *leftLine = nullptr; gs_vertbuffer_t *topLine = nullptr; gs_vertbuffer_t *rightLine = nullptr; - gs_effect_t *solid = nullptr; - gs_eparam_t *color = nullptr; // Multiview position helpers float thickness = 4; float offset, thicknessx2 = thickness * 2, pvwprgCX, pvwprgCY, sourceX, @@ -58,11 +56,6 @@ private: siX, siY, siCX, siCY, ppiScaleX, ppiScaleY, siScaleX, siScaleY, fw, fh, ratio; - float lineLength = 0.1f; - // Rec. ITU-R BT.1848-1 / EBU R 95 - float actionSafePercentage = 0.035f; // 3.5% - float graphicsSafePercentage = 0.05f; // 5.0% - float fourByThreeSafePercentage = 0.1625f; // 16.25% bool ready = false; // argb colors