diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini
index 9993538e1..9f553bcaf 100644
--- a/UI/data/locale/en-US.ini
+++ b/UI/data/locale/en-US.ini
@@ -1036,6 +1036,7 @@ Basic.Settings.Advanced.Video.ColorSpace="Color Space"
Basic.Settings.Advanced.Video.ColorRange="Color Range"
Basic.Settings.Advanced.Video.ColorRange.Partial="Partial"
Basic.Settings.Advanced.Video.ColorRange.Full="Full"
+Basic.Settings.Advanced.Video.SdrWhiteLevel="SDR White Level (nits)"
Basic.Settings.Advanced.Audio.MonitoringDevice="Monitoring Device"
Basic.Settings.Advanced.Audio.MonitoringDevice.Default="Default"
Basic.Settings.Advanced.Audio.DisableAudioDucking="Disable Windows audio ducking"
diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui
index 2f4c02e4a..b7b9712f6 100644
--- a/UI/forms/OBSBasicSettings.ui
+++ b/UI/forms/OBSBasicSettings.ui
@@ -4989,35 +4989,15 @@
- -
-
-
- 0
+
-
+
+
+ Basic.Settings.Advanced.Video.ColorSpace
-
- 0
+
+ colorSpace
-
- 0
-
-
- 0
-
-
-
-
-
- DisableOSXVSync
-
-
-
- -
-
-
- ResetOSXVSyncOnExit
-
-
-
-
+
-
@@ -5073,17 +5053,73 @@
- -
-
+
-
+
- Basic.Settings.Advanced.Video.ColorSpace
+ Basic.Settings.Advanced.Video.SdrWhiteLevel
- colorSpace
+ sdrWhiteLevel
- -
+
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 80
+
+
+ 480
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ DisableOSXVSync
+
+
+
+ -
+
+
+ ResetOSXVSyncOnExit
+
+
+
+
+
+ -
Qt::Horizontal
diff --git a/UI/qt-display.cpp b/UI/qt-display.cpp
index 7dca7ead4..06414c007 100644
--- a/UI/qt-display.cpp
+++ b/UI/qt-display.cpp
@@ -221,6 +221,14 @@ QPaintEngine *OBSQTDisplay::paintEngine() const
return nullptr;
}
-void OBSQTDisplay::OnMove() {}
+void OBSQTDisplay::OnMove()
+{
+ if (display)
+ obs_display_update_color_space(display);
+}
-void OBSQTDisplay::OnDisplayChange() {}
+void OBSQTDisplay::OnDisplayChange()
+{
+ if (display)
+ obs_display_update_color_space(display);
+}
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index b36c26f0e..98418aebd 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -1490,6 +1490,7 @@ bool OBSBasic::InitBasicConfigDefaults()
config_set_default_string(basicConfig, "Video", "ColorSpace", "709");
config_set_default_string(basicConfig, "Video", "ColorRange",
"Partial");
+ config_set_default_uint(basicConfig, "Video", "SdrWhiteLevel", 300);
config_set_default_string(basicConfig, "Audio", "MonitoringDeviceId",
"default");
@@ -4392,6 +4393,8 @@ int OBSBasic::ResetVideo()
}
if (ret == OBS_VIDEO_SUCCESS) {
+ obs_set_video_sdr_white_level((float)config_get_uint(
+ basicConfig, "Video", "SdrWhiteLevel"));
OBSBasicStats::InitializeValues();
OBSProjector::UpdateMultiviewProjectors();
}
diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp
index 5f2587ac6..c6d99d74b 100644
--- a/UI/window-basic-settings.cpp
+++ b/UI/window-basic-settings.cpp
@@ -531,6 +531,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
HookWidget(ui->colorFormat, COMBO_CHANGED, ADV_CHANGED);
HookWidget(ui->colorSpace, COMBO_CHANGED, ADV_CHANGED);
HookWidget(ui->colorRange, COMBO_CHANGED, ADV_CHANGED);
+ HookWidget(ui->sdrWhiteLevel, SCROLL_CHANGED, ADV_CHANGED);
HookWidget(ui->disableOSXVSync, CHECK_CHANGED, ADV_CHANGED);
HookWidget(ui->resetOSXVSync, CHECK_CHANGED, ADV_CHANGED);
if (obs_audio_monitoring_available())
@@ -2521,6 +2522,8 @@ void OBSBasicSettings::LoadAdvancedSettings()
config_get_string(main->Config(), "Video", "ColorSpace");
const char *videoColorRange =
config_get_string(main->Config(), "Video", "ColorRange");
+ uint32_t sdrWhiteLevel = (uint32_t)config_get_uint(
+ main->Config(), "Video", "SdrWhiteLevel");
QString monDevName;
QString monDevId;
@@ -2592,6 +2595,7 @@ void OBSBasicSettings::LoadAdvancedSettings()
SetComboByName(ui->colorFormat, videoColorFormat);
SetComboByName(ui->colorSpace, videoColorSpace);
SetComboByValue(ui->colorRange, videoColorRange);
+ ui->sdrWhiteLevel->setValue(sdrWhiteLevel);
if (!SetComboByValue(ui->bindToIP, bindIP))
SetInvalidValue(ui->bindToIP, bindIP, bindIP);
@@ -3297,6 +3301,7 @@ void OBSBasicSettings::SaveAdvancedSettings()
SaveCombo(ui->colorFormat, "Video", "ColorFormat");
SaveCombo(ui->colorSpace, "Video", "ColorSpace");
SaveComboData(ui->colorRange, "Video", "ColorRange");
+ SaveSpinBox(ui->sdrWhiteLevel, "Video", "SdrWhiteLevel");
if (obs_audio_monitoring_available()) {
SaveCombo(ui->monitoringDevice, "Audio",
"MonitoringDeviceName");
diff --git a/docs/sphinx/reference-core.rst b/docs/sphinx/reference-core.rst
index a01b61667..79ba786c5 100644
--- a/docs/sphinx/reference-core.rst
+++ b/docs/sphinx/reference-core.rst
@@ -147,11 +147,25 @@ Initialization, Shutdown, and Information
.. function:: bool obs_get_video_info(struct obs_video_info *ovi)
Gets the current video settings.
-
+
:return: *false* if no video
---------------------
+.. function:: float obs_get_video_sdr_white_level(void)
+
+ Gets the current SDR white level.
+
+ :return: SDR white level, 300.f if no video
+
+---------------------
+
+.. function:: void obs_set_video_sdr_white_level(float sdr_white_level)
+
+ Sets the current SDR white level.
+
+---------------------
+
.. function:: bool obs_get_audio_info(struct obs_audio_info *oai)
Gets the current audio settings.
diff --git a/docs/sphinx/reference-libobs-graphics-graphics.rst b/docs/sphinx/reference-libobs-graphics-graphics.rst
index d1f83bb28..b1245acca 100644
--- a/docs/sphinx/reference-libobs-graphics-graphics.rst
+++ b/docs/sphinx/reference-libobs-graphics-graphics.rst
@@ -47,6 +47,14 @@ Graphics Enumerations
- GS_BGRA_UNORM - BGRA, 8 bits per channel, no SRGB aliasing
- GS_RG16 - RG, 16 bits per channel
+.. type:: enum gs_color_space
+
+ Color space. Can be one of the following values:
+
+ - GS_CS_SRGB - sRGB
+ - GS_CS_709_EXTENDED - Canvas, Mac EDR (HDR)
+ - GS_CS_709_SCRGB - 1.0 = 80 nits, Windows/Linux HDR
+
.. type:: enum gs_zstencil_format
Z-Stencil buffer format. Can be one of the following values:
@@ -542,6 +550,13 @@ Swap Chains
---------------------
+.. function:: void gs_update_color_space(void)
+
+ Updates the color space of the swap chain based on the HDR status of
+ the nearest monitor
+
+---------------------
+
.. function:: void gs_get_size(uint32_t *cx, uint32_t *cy)
Gets the size of the currently active swap chain
@@ -613,6 +628,12 @@ Resource Loading
Draw Functions
--------------
+.. function:: enum gs_color_space gs_get_color_space(void)
+
+ :return: The currently active color space
+
+---------------------
+
.. function:: gs_texture_t *gs_get_render_target(void)
:return: The currently active render target
@@ -627,13 +648,23 @@ Draw Functions
.. function:: void gs_set_render_target(gs_texture_t *tex, gs_zstencil_t *zstencil)
- Sets the active render target
+ Sets the active render target with implicit GS_CS_SRGB color space
:param tex: Texture to set as the active render target
:param zstencil: Z-stencil to use as the active render target
---------------------
+.. function:: void gs_set_render_target_with_color_space(gs_texture_t *tex, gs_zstencil_t *zstencil, enum gs_color_space space)
+
+ Sets the active render target along with color space
+
+ :param tex: Texture to set as the active render target
+ :param zstencil: Z-stencil to use as the active render target
+ :param space: Color space of the render target
+
+---------------------
+
.. function:: void gs_set_cube_render_target(gs_texture_t *cubetex, int side, gs_zstencil_t *zstencil)
Sets a cubemap side as the active render target
@@ -1475,6 +1506,14 @@ Display Duplicator (Windows Only)
---------------------
+Monitor Functions
+---------------------------------
+
+.. function:: bool gs_is_monitor_hdr(void *monitor)
+
+---------------------
+
+
Render Helper Functions
-----------------------
diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp
index 40d52627b..3b858b8a1 100644
--- a/libobs-d3d11/d3d11-subsystem.cpp
+++ b/libobs-d3d11/d3d11-subsystem.cpp
@@ -66,21 +66,78 @@ gs_obj::~gs_obj()
next->prev_next = prev_next;
}
-static inline void make_swap_desc(DXGI_SWAP_CHAIN_DESC &desc,
- const gs_init_data *data,
- DXGI_SWAP_EFFECT effect, UINT flags)
+static bool screen_supports_hdr(gs_device_t *device, HMONITOR hMonitor)
{
+ IDXGIFactory1 *factory1 = device->factory;
+ if (!factory1->IsCurrent()) {
+ device->InitFactory();
+ factory1 = device->factory;
+ }
+
+ ComPtr adapter;
+ ComPtr output;
+ ComPtr output6;
+ for (UINT adapterIndex = 0;
+ SUCCEEDED(factory1->EnumAdapters(adapterIndex, &adapter));
+ ++adapterIndex) {
+ for (UINT outputIndex = 0;
+ SUCCEEDED(adapter->EnumOutputs(outputIndex, &output));
+ ++outputIndex) {
+ if (SUCCEEDED(output->QueryInterface(&output6))) {
+ DXGI_OUTPUT_DESC1 desc1;
+ if (SUCCEEDED(output6->GetDesc1(&desc1)) &&
+ desc1.Monitor == hMonitor) {
+ return desc1.ColorSpace ==
+ DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static enum gs_color_space get_next_space(gs_device_t *device, HWND hwnd)
+{
+ enum gs_color_space next_space = GS_CS_SRGB;
+ const HMONITOR hMonitor =
+ MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+ if (hMonitor) {
+ if (screen_supports_hdr(device, hMonitor))
+ next_space = GS_CS_709_SCRGB;
+ }
+
+ return next_space;
+}
+
+static enum gs_color_format
+get_swap_format_from_space(gs_color_space space, gs_color_format sdr_format)
+{
+ return (space == GS_CS_709_SCRGB) ? GS_RGBA16F : sdr_format;
+}
+
+static inline enum gs_color_space
+make_swap_desc(gs_device *device, DXGI_SWAP_CHAIN_DESC &desc,
+ const gs_init_data *data, DXGI_SWAP_EFFECT effect, UINT flags)
+{
+ const HWND hwnd = (HWND)data->window.hwnd;
+ const enum gs_color_space space = get_next_space(device, hwnd);
+ const gs_color_format format =
+ get_swap_format_from_space(space, data->format);
+
memset(&desc, 0, sizeof(desc));
desc.BufferDesc.Width = data->cx;
desc.BufferDesc.Height = data->cy;
- desc.BufferDesc.Format = ConvertGSTextureFormatView(data->format);
+ desc.BufferDesc.Format = ConvertGSTextureFormatView(format);
desc.SampleDesc.Count = 1;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = data->num_backbuffers;
- desc.OutputWindow = (HWND)data->window.hwnd;
+ desc.OutputWindow = hwnd;
desc.Windowed = TRUE;
desc.SwapEffect = effect;
desc.Flags = flags;
+
+ return space;
}
void gs_swap_chain::InitTarget(uint32_t cx, uint32_t cy)
@@ -128,7 +185,7 @@ void gs_swap_chain::InitZStencilBuffer(uint32_t cx, uint32_t cy)
}
}
-void gs_swap_chain::Resize(uint32_t cx, uint32_t cy)
+void gs_swap_chain::Resize(uint32_t cx, uint32_t cy, gs_color_format format)
{
RECT clientRect;
HRESULT hr;
@@ -150,25 +207,40 @@ void gs_swap_chain::Resize(uint32_t cx, uint32_t cy)
cy = clientRect.bottom;
}
- hr = swap->ResizeBuffers(swapDesc.BufferCount, cx, cy,
- DXGI_FORMAT_UNKNOWN, swapDesc.Flags);
+ const DXGI_FORMAT dxgi_format = ConvertGSTextureFormatView(format);
+ hr = swap->ResizeBuffers(swapDesc.BufferCount, cx, cy, dxgi_format,
+ swapDesc.Flags);
if (FAILED(hr))
throw HRError("Failed to resize swap buffers", hr);
+ ComQIPtr swap3 = swap;
+ if (swap3) {
+ const DXGI_COLOR_SPACE_TYPE dxgi_space =
+ (format == GS_RGBA16F)
+ ? DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709
+ : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ hr = swap3->SetColorSpace1(dxgi_space);
+ if (FAILED(hr))
+ throw HRError("Failed to set color space", hr);
+ }
+ target.dxgiFormatResource = ConvertGSTextureFormatResource(format);
+ target.dxgiFormatView = dxgi_format;
+ target.dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format);
InitTarget(cx, cy);
InitZStencilBuffer(cx, cy);
}
void gs_swap_chain::Init()
{
+ const gs_color_format format = get_swap_format_from_space(
+ get_next_space(device, hwnd), initData.format);
+
target.device = device;
target.isRenderTarget = true;
target.format = initData.format;
- target.dxgiFormatResource =
- ConvertGSTextureFormatResource(initData.format);
- target.dxgiFormatView = ConvertGSTextureFormatView(initData.format);
- target.dxgiFormatViewLinear =
- ConvertGSTextureFormatViewLinear(initData.format);
+ target.dxgiFormatResource = ConvertGSTextureFormatResource(format);
+ target.dxgiFormatView = ConvertGSTextureFormatView(format);
+ target.dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format);
InitTarget(initData.cx, initData.cy);
zs.device = device;
@@ -180,7 +252,8 @@ void gs_swap_chain::Init()
gs_swap_chain::gs_swap_chain(gs_device *device, const gs_init_data *data)
: gs_obj(device, gs_type::gs_swap_chain),
hwnd((HWND)data->window.hwnd),
- initData(*data)
+ initData(*data),
+ space(GS_CS_SRGB)
{
DXGI_SWAP_EFFECT effect = DXGI_SWAP_EFFECT_DISCARD;
UINT flags = 0;
@@ -203,7 +276,7 @@ gs_swap_chain::gs_swap_chain(gs_device *device, const gs_init_data *data)
}
}
- make_swap_desc(swapDesc, &initData, effect, flags);
+ space = make_swap_desc(device, swapDesc, &initData, effect, flags);
HRESULT hr = device->factory->CreateSwapChain(device->device, &swapDesc,
swap.Assign());
if (FAILED(hr))
@@ -1340,6 +1413,24 @@ gs_swapchain_t *device_swapchain_create(gs_device_t *device,
return swap;
}
+static void device_resize_internal(gs_device_t *device, uint32_t cx,
+ uint32_t cy, gs_color_space space)
+{
+ try {
+ const gs_color_format format = get_swap_format_from_space(
+ space, device->curSwapChain->initData.format);
+
+ device->context->OMSetRenderTargets(0, NULL, NULL);
+ device->curSwapChain->Resize(cx, cy, format);
+ device->curSwapChain->space = space;
+ device->curFramebufferInvalidate = true;
+ } catch (const HRError &error) {
+ blog(LOG_ERROR, "device_resize_internal (D3D11): %s (%08lX)",
+ error.str, error.hr);
+ LogD3D11ErrorDetails(error, device);
+ }
+}
+
void device_resize(gs_device_t *device, uint32_t cx, uint32_t cy)
{
if (!device->curSwapChain) {
@@ -1347,14 +1438,26 @@ void device_resize(gs_device_t *device, uint32_t cx, uint32_t cy)
return;
}
- try {
- device->context->OMSetRenderTargets(0, NULL, NULL);
- device->curSwapChain->Resize(cx, cy);
- device->curFramebufferInvalidate = true;
- } catch (const HRError &error) {
- blog(LOG_ERROR, "device_resize (D3D11): %s (%08lX)", error.str,
- error.hr);
- LogD3D11ErrorDetails(error, device);
+ const enum gs_color_space next_space =
+ get_next_space(device, device->curSwapChain->hwnd);
+ device_resize_internal(device, cx, cy, next_space);
+}
+
+enum gs_color_space device_get_color_space(gs_device_t *device)
+{
+ return device->curColorSpace;
+}
+
+void device_update_color_space(gs_device_t *device)
+{
+ if (device->curSwapChain) {
+ const enum gs_color_space next_space =
+ get_next_space(device, device->curSwapChain->hwnd);
+ if (device->curSwapChain->space != next_space)
+ device_resize_internal(device, 0, 0, next_space);
+ } else {
+ blog(LOG_WARNING,
+ "device_update_color_space (D3D11): No active swap");
}
}
@@ -1874,8 +1977,10 @@ gs_zstencil_t *device_get_zstencil_target(const gs_device_t *device)
return device->curZStencilBuffer;
}
-void device_set_render_target(gs_device_t *device, gs_texture_t *tex,
- gs_zstencil_t *zstencil)
+static void device_set_render_target_internal(gs_device_t *device,
+ gs_texture_t *tex,
+ gs_zstencil_t *zstencil,
+ enum gs_color_space space)
{
if (device->curSwapChain) {
if (!tex)
@@ -1885,12 +1990,13 @@ void device_set_render_target(gs_device_t *device, gs_texture_t *tex,
}
if (device->curRenderTarget == tex &&
- device->curZStencilBuffer == zstencil)
- return;
+ device->curZStencilBuffer == zstencil) {
+ device->curColorSpace = space;
+ }
if (tex && tex->type != GS_TEXTURE_2D) {
blog(LOG_ERROR,
- "device_set_render_target (D3D11): texture is not a 2D texture");
+ "device_set_render_target_internal (D3D11): texture is not a 2D texture");
return;
}
@@ -1898,12 +2004,27 @@ void device_set_render_target(gs_device_t *device, gs_texture_t *tex,
if (device->curRenderTarget != tex2d || device->curRenderSide != 0 ||
device->curZStencilBuffer != zstencil) {
device->curRenderTarget = tex2d;
- device->curRenderSide = 0;
device->curZStencilBuffer = zstencil;
+ device->curRenderSide = 0;
+ device->curColorSpace = space;
device->curFramebufferInvalidate = true;
}
}
+void device_set_render_target(gs_device_t *device, gs_texture_t *tex,
+ gs_zstencil_t *zstencil)
+{
+ device_set_render_target_internal(device, tex, zstencil, GS_CS_SRGB);
+}
+
+void device_set_render_target_with_color_space(gs_device_t *device,
+ gs_texture_t *tex,
+ gs_zstencil_t *zstencil,
+ enum gs_color_space space)
+{
+ device_set_render_target_internal(device, tex, zstencil, space);
+}
+
void device_set_cube_render_target(gs_device_t *device, gs_texture_t *tex,
int side, gs_zstencil_t *zstencil)
{
@@ -1931,8 +2052,9 @@ void device_set_cube_render_target(gs_device_t *device, gs_texture_t *tex,
if (device->curRenderTarget != tex2d || device->curRenderSide != side ||
device->curZStencilBuffer != zstencil) {
device->curRenderTarget = tex2d;
- device->curRenderSide = side;
device->curZStencilBuffer = zstencil;
+ device->curRenderSide = side;
+ device->curColorSpace = GS_CS_SRGB;
device->curFramebufferInvalidate = true;
}
}
@@ -2177,11 +2299,14 @@ void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swapchain)
device->curSwapChain = swapchain;
- if (is_cube)
+ if (is_cube) {
device_set_cube_render_target(device, target,
device->curRenderSide, zs);
- else
- device_set_render_target(device, target, zs);
+ } else {
+ const enum gs_color_space space = swapchain ? swapchain->space
+ : GS_CS_SRGB;
+ device_set_render_target_internal(device, target, zs, space);
+ }
}
void device_clear(gs_device_t *device, uint32_t clear_flags,
@@ -2922,6 +3047,12 @@ extern "C" EXPORT bool device_p010_available(gs_device_t *device)
return device->p010Supported;
}
+extern "C" EXPORT bool device_is_monitor_hdr(gs_device_t *device, void *monitor)
+{
+ const HMONITOR hMonitor = static_cast(monitor);
+ return screen_supports_hdr(device, hMonitor);
+}
+
extern "C" EXPORT void device_debug_marker_begin(gs_device_t *,
const char *markername,
const float color[4])
diff --git a/libobs-d3d11/d3d11-subsystem.hpp b/libobs-d3d11/d3d11-subsystem.hpp
index f693e52f4..34297e00b 100644
--- a/libobs-d3d11/d3d11-subsystem.hpp
+++ b/libobs-d3d11/d3d11-subsystem.hpp
@@ -820,6 +820,7 @@ struct gs_swap_chain : gs_obj {
HWND hwnd;
gs_init_data initData;
DXGI_SWAP_CHAIN_DESC swapDesc = {};
+ gs_color_space space;
UINT presentFlags = 0;
gs_texture_2d target;
@@ -829,7 +830,7 @@ struct gs_swap_chain : gs_obj {
void InitTarget(uint32_t cx, uint32_t cy);
void InitZStencilBuffer(uint32_t cx, uint32_t cy);
- void Resize(uint32_t cx, uint32_t cy);
+ void Resize(uint32_t cx, uint32_t cy, gs_color_format format);
void Init();
void Rebuild(ID3D11Device *dev);
@@ -991,6 +992,7 @@ struct gs_device {
gs_texture_2d *curRenderTarget = nullptr;
gs_zstencil_buffer *curZStencilBuffer = nullptr;
int curRenderSide = 0;
+ enum gs_color_space curColorSpace = GS_CS_SRGB;
bool curFramebufferSrgb = false;
bool curFramebufferInvalidate = false;
gs_texture *curTextures[GS_MAX_TEXTURES];
diff --git a/libobs-opengl/gl-cocoa.m b/libobs-opengl/gl-cocoa.m
index 83a245fe6..81f63ca99 100644
--- a/libobs-opengl/gl-cocoa.m
+++ b/libobs-opengl/gl-cocoa.m
@@ -310,6 +310,11 @@ void device_present(gs_device_t *device)
[device->plat->context makeCurrentContext];
}
+bool device_is_monitor_hdr(gs_device_t *device, void *monitor)
+{
+ return false;
+}
+
void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width,
uint32_t *height)
{
diff --git a/libobs-opengl/gl-nix.c b/libobs-opengl/gl-nix.c
index e387eaa4b..e87eb007e 100644
--- a/libobs-opengl/gl-nix.c
+++ b/libobs-opengl/gl-nix.c
@@ -124,6 +124,11 @@ extern void device_present(gs_device_t *device)
gl_vtable->device_present(device);
}
+extern bool device_is_monitor_hdr(gs_device_t *device, void *monitor)
+{
+ return false;
+}
+
extern struct gs_texture *device_texture_create_from_dmabuf(
gs_device_t *device, unsigned int width, unsigned int height,
uint32_t drm_format, enum gs_color_format color_format,
diff --git a/libobs-opengl/gl-subsystem.c b/libobs-opengl/gl-subsystem.c
index e49c89788..d8969b329 100644
--- a/libobs-opengl/gl-subsystem.c
+++ b/libobs-opengl/gl-subsystem.c
@@ -340,6 +340,17 @@ void device_resize(gs_device_t *device, uint32_t cx, uint32_t cy)
gl_update(device);
}
+enum gs_color_space device_get_color_space(gs_device_t *device)
+{
+ return device->cur_color_space;
+}
+
+void device_update_color_space(gs_device_t *device)
+{
+ if (!device->cur_swap)
+ blog(LOG_WARNING, "device_display_change (GL): No active swap");
+}
+
void device_get_size(const gs_device_t *device, uint32_t *cx, uint32_t *cy)
{
if (device->cur_swap) {
@@ -819,9 +830,9 @@ static bool attach_zstencil(struct fbo_info *fbo, gs_zstencil_t *zs)
}
static bool set_target(gs_device_t *device, gs_texture_t *tex, int side,
- gs_zstencil_t *zs)
+ gs_zstencil_t *zs, enum gs_color_space space)
{
- struct fbo_info *fbo;
+ device->cur_color_space = space;
if (device->cur_render_target == tex &&
device->cur_zstencil_buffer == zs &&
@@ -835,7 +846,7 @@ static bool set_target(gs_device_t *device, gs_texture_t *tex, int side,
if (!tex)
return set_current_fbo(device, NULL);
- fbo = get_fbo_by_tex(tex);
+ struct fbo_info *const fbo = get_fbo_by_tex(tex);
if (!fbo)
return false;
@@ -864,7 +875,7 @@ void device_set_render_target(gs_device_t *device, gs_texture_t *tex,
}
}
- if (!set_target(device, tex, 0, zstencil))
+ if (!set_target(device, tex, 0, zstencil, GS_CS_SRGB))
goto fail;
return;
@@ -873,6 +884,33 @@ fail:
blog(LOG_ERROR, "device_set_render_target (GL) failed");
}
+void device_set_render_target_with_color_space(gs_device_t *device,
+ gs_texture_t *tex,
+ gs_zstencil_t *zstencil,
+ enum gs_color_space space)
+{
+ if (tex) {
+ if (tex->type != GS_TEXTURE_2D) {
+ blog(LOG_ERROR, "Texture is not a 2D texture");
+ goto fail;
+ }
+
+ if (!tex->is_render_target) {
+ blog(LOG_ERROR, "Texture is not a render target");
+ goto fail;
+ }
+ }
+
+ if (!set_target(device, tex, 0, zstencil, space))
+ goto fail;
+
+ return;
+
+fail:
+ blog(LOG_ERROR,
+ "device_set_render_target_with_color_space (GL) failed");
+}
+
void device_set_cube_render_target(gs_device_t *device, gs_texture_t *cubetex,
int side, gs_zstencil_t *zstencil)
{
@@ -888,7 +926,7 @@ void device_set_cube_render_target(gs_device_t *device, gs_texture_t *cubetex,
}
}
- if (!set_target(device, cubetex, side, zstencil))
+ if (!set_target(device, cubetex, side, zstencil, GS_CS_SRGB))
goto fail;
return;
diff --git a/libobs-opengl/gl-subsystem.h b/libobs-opengl/gl-subsystem.h
index 513ba179a..daddffffc 100644
--- a/libobs-opengl/gl-subsystem.h
+++ b/libobs-opengl/gl-subsystem.h
@@ -653,6 +653,7 @@ struct gs_device {
gs_shader_t *cur_pixel_shader;
gs_swapchain_t *cur_swap;
struct gs_program *cur_program;
+ enum gs_color_space cur_color_space;
struct gs_program *first_program;
diff --git a/libobs-opengl/gl-windows.c b/libobs-opengl/gl-windows.c
index 61c80c83c..d9fcd28a2 100644
--- a/libobs-opengl/gl-windows.c
+++ b/libobs-opengl/gl-windows.c
@@ -598,6 +598,11 @@ extern void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width,
}
}
+EXPORT bool device_is_monitor_hdr(gs_device_t *device, void *monitor)
+{
+ return false;
+}
+
EXPORT bool device_gdi_texture_available(void)
{
return false;
diff --git a/libobs/data/default.effect b/libobs/data/default.effect
index db4cfe2ae..d5785469a 100644
--- a/libobs/data/default.effect
+++ b/libobs/data/default.effect
@@ -1,5 +1,6 @@
uniform float4x4 ViewProj;
uniform texture2d image;
+uniform float multiplier;
sampler_state def_sampler {
Filter = Linear;
@@ -69,6 +70,48 @@ float4 PSDrawSrgbDecompress(VertInOut vert_in) : TARGET
return rgba;
}
+float4 PSDrawMultiply(VertInOut vert_in) : TARGET
+{
+ float4 rgba = image.Sample(def_sampler, vert_in.uv);
+ rgba.rgb *= multiplier;
+ return rgba;
+}
+
+float3 rec709_to_rec2020(float3 v)
+{
+ float r = dot(v, float3(0.6274040f, 0.3292820f, 0.0433136f));
+ float g = dot(v, float3(0.0690970f, 0.9195400f, 0.0113612f));
+ float b = dot(v, float3(0.0163916f, 0.0880132f, 0.8955950f));
+ return float3(r, g, b);
+}
+
+float3 rec2020_to_rec709(float3 v)
+{
+ float r = dot(v, float3(1.6604910, -0.5876411, -0.0728499));
+ float g = dot(v, float3(-0.1245505, 1.1328999, -0.0083494));
+ float b = dot(v, float3(-0.0181508, -0.1005789, 1.1187297));
+ return float3(r, g, b);
+}
+
+float reinhard_channel(float x)
+{
+ return x / (x + 1.0);
+}
+
+float3 reinhard(float3 rgb)
+{
+ return float3(reinhard_channel(rgb.r), reinhard_channel(rgb.g), reinhard_channel(rgb.b));
+}
+
+float4 PSDrawTonemap(VertInOut vert_in) : TARGET
+{
+ float4 rgba = image.Sample(def_sampler, vert_in.uv);
+ rgba.rgb = rec709_to_rec2020(rgba.rgb);
+ rgba.rgb = reinhard(rgba.rgb);
+ rgba.rgb = rec2020_to_rec709(rgba.rgb);
+ return rgba;
+}
+
technique Draw
{
pass
@@ -104,3 +147,21 @@ technique DrawSrgbDecompress
pixel_shader = PSDrawSrgbDecompress(vert_in);
}
}
+
+technique DrawMultiply
+{
+ pass
+ {
+ vertex_shader = VSDefault(vert_in);
+ pixel_shader = PSDrawMultiply(vert_in);
+ }
+}
+
+technique DrawTonemap
+{
+ pass
+ {
+ vertex_shader = VSDefault(vert_in);
+ pixel_shader = PSDrawTonemap(vert_in);
+ }
+}
diff --git a/libobs/graphics/device-exports.h b/libobs/graphics/device-exports.h
index 55ebaf2be..290d33f94 100644
--- a/libobs/graphics/device-exports.h
+++ b/libobs/graphics/device-exports.h
@@ -37,6 +37,8 @@ EXPORT void *device_get_device_obj(gs_device_t *device);
EXPORT gs_swapchain_t *device_swapchain_create(gs_device_t *device,
const struct gs_init_data *data);
EXPORT void device_resize(gs_device_t *device, uint32_t x, uint32_t y);
+EXPORT enum gs_color_space device_get_color_space(gs_device_t *device);
+EXPORT void device_update_color_space(gs_device_t *device);
EXPORT void device_get_size(const gs_device_t *device, uint32_t *x,
uint32_t *y);
EXPORT uint32_t device_get_width(const gs_device_t *device);
@@ -104,6 +106,9 @@ EXPORT gs_texture_t *device_get_render_target(const gs_device_t *device);
EXPORT gs_zstencil_t *device_get_zstencil_target(const gs_device_t *device);
EXPORT void device_set_render_target(gs_device_t *device, gs_texture_t *tex,
gs_zstencil_t *zstencil);
+EXPORT void device_set_render_target_with_color_space(
+ gs_device_t *device, gs_texture_t *tex, gs_zstencil_t *zstencil,
+ enum gs_color_space space);
EXPORT void device_set_cube_render_target(gs_device_t *device,
gs_texture_t *cubetex, int side,
gs_zstencil_t *zstencil);
diff --git a/libobs/graphics/graphics-imports.c b/libobs/graphics/graphics-imports.c
index 7b92b2994..599a34ba8 100644
--- a/libobs/graphics/graphics-imports.c
+++ b/libobs/graphics/graphics-imports.c
@@ -53,6 +53,8 @@ bool load_graphics_imports(struct gs_exports *exports, void *module,
GRAPHICS_IMPORT(device_get_device_obj);
GRAPHICS_IMPORT(device_swapchain_create);
GRAPHICS_IMPORT(device_resize);
+ GRAPHICS_IMPORT(device_get_color_space);
+ GRAPHICS_IMPORT(device_update_color_space);
GRAPHICS_IMPORT(device_get_size);
GRAPHICS_IMPORT(device_get_width);
GRAPHICS_IMPORT(device_get_height);
@@ -81,6 +83,7 @@ bool load_graphics_imports(struct gs_exports *exports, void *module,
GRAPHICS_IMPORT(device_get_render_target);
GRAPHICS_IMPORT(device_get_zstencil_target);
GRAPHICS_IMPORT(device_set_render_target);
+ GRAPHICS_IMPORT(device_set_render_target_with_color_space);
GRAPHICS_IMPORT(device_set_cube_render_target);
GRAPHICS_IMPORT(device_enable_framebuffer_srgb);
GRAPHICS_IMPORT(device_framebuffer_srgb_enabled);
@@ -192,6 +195,8 @@ bool load_graphics_imports(struct gs_exports *exports, void *module,
GRAPHICS_IMPORT_OPTIONAL(device_nv12_available);
GRAPHICS_IMPORT_OPTIONAL(device_p010_available);
+ GRAPHICS_IMPORT(device_is_monitor_hdr);
+
GRAPHICS_IMPORT(device_debug_marker_begin);
GRAPHICS_IMPORT(device_debug_marker_end);
diff --git a/libobs/graphics/graphics-internal.h b/libobs/graphics/graphics-internal.h
index 706632478..8b64d2106 100644
--- a/libobs/graphics/graphics-internal.h
+++ b/libobs/graphics/graphics-internal.h
@@ -38,6 +38,8 @@ struct gs_exports {
gs_swapchain_t *(*device_swapchain_create)(
gs_device_t *device, const struct gs_init_data *data);
void (*device_resize)(gs_device_t *device, uint32_t x, uint32_t y);
+ enum gs_color_space (*device_get_color_space)(gs_device_t *device);
+ void (*device_update_color_space)(gs_device_t *device);
void (*device_get_size)(const gs_device_t *device, uint32_t *x,
uint32_t *y);
uint32_t (*device_get_width)(const gs_device_t *device);
@@ -103,6 +105,9 @@ struct gs_exports {
gs_zstencil_t *(*device_get_zstencil_target)(const gs_device_t *device);
void (*device_set_render_target)(gs_device_t *device, gs_texture_t *tex,
gs_zstencil_t *zstencil);
+ void (*device_set_render_target_with_color_space)(
+ gs_device_t *device, gs_texture_t *tex, gs_zstencil_t *zstencil,
+ enum gs_color_space space);
void (*device_set_cube_render_target)(gs_device_t *device,
gs_texture_t *cubetex, int side,
gs_zstencil_t *zstencil);
@@ -268,6 +273,8 @@ struct gs_exports {
bool (*device_nv12_available)(gs_device_t *device);
bool (*device_p010_available)(gs_device_t *device);
+ bool (*device_is_monitor_hdr)(gs_device_t *device, void *monitor);
+
void (*device_debug_marker_begin)(gs_device_t *device,
const char *markername,
const float color[4]);
diff --git a/libobs/graphics/graphics.c b/libobs/graphics/graphics.c
index e931fe8d5..7fae78fc8 100644
--- a/libobs/graphics/graphics.c
+++ b/libobs/graphics/graphics.c
@@ -1308,6 +1308,16 @@ void gs_resize(uint32_t x, uint32_t y)
graphics->exports.device_resize(graphics->device, x, y);
}
+void gs_update_color_space(void)
+{
+ graphics_t *graphics = thread_graphics;
+
+ if (!gs_valid("gs_update_color_space"))
+ return;
+
+ graphics->exports.device_update_color_space(graphics->device);
+}
+
void gs_get_size(uint32_t *x, uint32_t *y)
{
graphics_t *graphics = thread_graphics;
@@ -1715,6 +1725,16 @@ gs_shader_t *gs_get_pixel_shader(void)
return graphics->exports.device_get_pixel_shader(graphics->device);
}
+enum gs_color_space gs_get_color_space(void)
+{
+ graphics_t *graphics = thread_graphics;
+
+ if (!gs_valid("gs_get_color_space"))
+ return GS_CS_SRGB;
+
+ return graphics->exports.device_get_color_space(graphics->device);
+}
+
gs_texture_t *gs_get_render_target(void)
{
graphics_t *graphics = thread_graphics;
@@ -1746,6 +1766,19 @@ void gs_set_render_target(gs_texture_t *tex, gs_zstencil_t *zstencil)
zstencil);
}
+void gs_set_render_target_with_color_space(gs_texture_t *tex,
+ gs_zstencil_t *zstencil,
+ enum gs_color_space space)
+{
+ graphics_t *graphics = thread_graphics;
+
+ if (!gs_valid("gs_set_render_target_with_color_space"))
+ return;
+
+ graphics->exports.device_set_render_target_with_color_space(
+ graphics->device, tex, zstencil, space);
+}
+
void gs_set_cube_render_target(gs_texture_t *cubetex, int side,
gs_zstencil_t *zstencil)
{
@@ -2807,6 +2840,15 @@ bool gs_p010_available(void)
thread_graphics->device);
}
+bool gs_is_monitor_hdr(void *monitor)
+{
+ if (!gs_valid("gs_is_monitor_hdr"))
+ return false;
+
+ return thread_graphics->exports.device_is_monitor_hdr(
+ thread_graphics->device, monitor);
+}
+
void gs_debug_marker_begin(const float color[4], const char *markername)
{
if (!gs_valid("gs_debug_marker_begin"))
diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h
index 35d2c8687..aa11dc874 100644
--- a/libobs/graphics/graphics.h
+++ b/libobs/graphics/graphics.h
@@ -79,6 +79,12 @@ enum gs_color_format {
GS_RG16,
};
+enum gs_color_space {
+ GS_CS_SRGB, /* SDR */
+ GS_CS_709_EXTENDED, /* Canvas, Mac EDR (HDR) */
+ GS_CS_709_SCRGB, /* 1.0 = 80 nits, Windows/Linux HDR */
+};
+
enum gs_zstencil_format {
GS_ZS_NONE,
GS_Z16,
@@ -461,6 +467,9 @@ EXPORT gs_texrender_t *gs_texrender_create(enum gs_color_format format,
EXPORT void gs_texrender_destroy(gs_texrender_t *texrender);
EXPORT bool gs_texrender_begin(gs_texrender_t *texrender, uint32_t cx,
uint32_t cy);
+EXPORT bool gs_texrender_begin_with_color_space(gs_texrender_t *texrender,
+ uint32_t cx, uint32_t cy,
+ enum gs_color_space space);
EXPORT void gs_texrender_end(gs_texrender_t *texrender);
EXPORT void gs_texrender_reset(gs_texrender_t *texrender);
EXPORT gs_texture_t *gs_texrender_get_texture(const gs_texrender_t *texrender);
@@ -634,6 +643,7 @@ EXPORT void gs_reset_blend_state(void);
EXPORT gs_swapchain_t *gs_swapchain_create(const struct gs_init_data *data);
EXPORT void gs_resize(uint32_t x, uint32_t y);
+EXPORT void gs_update_color_space(void);
EXPORT void gs_get_size(uint32_t *x, uint32_t *y);
EXPORT uint32_t gs_get_width(void);
EXPORT uint32_t gs_get_height(void);
@@ -689,10 +699,14 @@ EXPORT void gs_load_default_samplerstate(bool b_3d, int unit);
EXPORT gs_shader_t *gs_get_vertex_shader(void);
EXPORT gs_shader_t *gs_get_pixel_shader(void);
+EXPORT enum gs_color_space gs_get_color_space(void);
EXPORT gs_texture_t *gs_get_render_target(void);
EXPORT gs_zstencil_t *gs_get_zstencil_target(void);
EXPORT void gs_set_render_target(gs_texture_t *tex, gs_zstencil_t *zstencil);
+EXPORT void gs_set_render_target_with_color_space(gs_texture_t *tex,
+ gs_zstencil_t *zstencil,
+ enum gs_color_space space);
EXPORT void gs_set_cube_render_target(gs_texture_t *cubetex, int side,
gs_zstencil_t *zstencil);
@@ -837,6 +851,8 @@ EXPORT bool gs_timer_range_get_data(gs_timer_range_t *range, bool *disjoint,
EXPORT bool gs_nv12_available(void);
EXPORT bool gs_p010_available(void);
+EXPORT bool gs_is_monitor_hdr(void *monitor);
+
#define GS_USE_DEBUG_MARKERS 0
#if GS_USE_DEBUG_MARKERS
static const float GS_DEBUG_COLOR_DEFAULT[] = {0.5f, 0.5f, 0.5f, 1.0f};
@@ -1039,6 +1055,20 @@ gs_generalize_format(enum gs_color_format format)
}
}
+static inline enum gs_color_format
+gs_get_format_from_space(enum gs_color_space space)
+{
+ switch (space) {
+ case GS_CS_SRGB:
+ break;
+ case GS_CS_709_EXTENDED:
+ case GS_CS_709_SCRGB:
+ return GS_RGBA16F;
+ }
+
+ return GS_RGBA;
+}
+
static inline uint32_t gs_get_total_levels(uint32_t width, uint32_t height,
uint32_t depth)
{
diff --git a/libobs/graphics/texture-render.c b/libobs/graphics/texture-render.c
index e06b21f24..476073178 100644
--- a/libobs/graphics/texture-render.c
+++ b/libobs/graphics/texture-render.c
@@ -26,6 +26,7 @@
struct gs_texture_render {
gs_texture_t *target, *prev_target;
gs_zstencil_t *zs, *prev_zs;
+ enum gs_color_space prev_space;
uint32_t cx, cy;
@@ -88,6 +89,13 @@ static bool texrender_resetbuffer(gs_texrender_t *texrender, uint32_t cx,
}
bool gs_texrender_begin(gs_texrender_t *texrender, uint32_t cx, uint32_t cy)
+{
+ return gs_texrender_begin_with_color_space(texrender, cx, cy,
+ GS_CS_SRGB);
+}
+
+bool gs_texrender_begin_with_color_space(gs_texrender_t *texrender, uint32_t cx,
+ uint32_t cy, enum gs_color_space space)
{
if (!texrender || texrender->rendered)
return false;
@@ -109,7 +117,9 @@ bool gs_texrender_begin(gs_texrender_t *texrender, uint32_t cx, uint32_t cy)
texrender->prev_target = gs_get_render_target();
texrender->prev_zs = gs_get_zstencil_target();
- gs_set_render_target(texrender->target, texrender->zs);
+ texrender->prev_space = gs_get_color_space();
+ gs_set_render_target_with_color_space(texrender->target, texrender->zs,
+ space);
gs_set_viewport(0, 0, texrender->cx, texrender->cy);
@@ -121,7 +131,9 @@ void gs_texrender_end(gs_texrender_t *texrender)
if (!texrender)
return;
- gs_set_render_target(texrender->prev_target, texrender->prev_zs);
+ gs_set_render_target_with_color_space(texrender->prev_target,
+ texrender->prev_zs,
+ texrender->prev_space);
gs_matrix_pop();
gs_projection_pop();
diff --git a/libobs/obs-display.c b/libobs/obs-display.c
index 0237f41e7..9f12a2b47 100644
--- a/libobs/obs-display.c
+++ b/libobs/obs-display.c
@@ -124,6 +124,18 @@ void obs_display_resize(obs_display_t *display, uint32_t cx, uint32_t cy)
pthread_mutex_unlock(&display->draw_info_mutex);
}
+void obs_display_update_color_space(obs_display_t *display)
+{
+ if (!display)
+ return;
+
+ pthread_mutex_lock(&display->draw_info_mutex);
+
+ display->update_color_space = true;
+
+ pthread_mutex_unlock(&display->draw_info_mutex);
+}
+
void obs_display_add_draw_callback(obs_display_t *display,
void (*draw)(void *param, uint32_t cx,
uint32_t cy),
@@ -155,7 +167,8 @@ void obs_display_remove_draw_callback(obs_display_t *display,
}
static inline void render_display_begin(struct obs_display *display,
- uint32_t cx, uint32_t cy)
+ uint32_t cx, uint32_t cy,
+ bool update_color_space)
{
struct vec4 clear_color;
@@ -165,11 +178,16 @@ static inline void render_display_begin(struct obs_display *display,
gs_resize(cx, cy);
display->cx = cx;
display->cy = cy;
+ } else if (update_color_space) {
+ gs_update_color_space();
}
gs_begin_scene();
- vec4_from_rgba(&clear_color, display->background_color);
+ if (gs_get_color_space() == GS_CS_SRGB)
+ vec4_from_rgba(&clear_color, display->background_color);
+ else
+ vec4_from_rgba_srgb(&clear_color, display->background_color);
clear_color.w = 1.0f;
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH | GS_CLEAR_STENCIL,
@@ -191,6 +209,7 @@ static inline void render_display_end()
void render_display(struct obs_display *display)
{
uint32_t cx, cy;
+ bool update_color_space;
if (!display || !display->enabled)
return;
@@ -203,12 +222,15 @@ void render_display(struct obs_display *display)
cx = display->next_cx;
cy = display->next_cy;
+ update_color_space = display->update_color_space;
+
+ display->update_color_space = false;
pthread_mutex_unlock(&display->draw_info_mutex);
/* -------------------------------------------- */
- render_display_begin(display, cx, cy);
+ render_display_begin(display, cx, cy, update_color_space);
pthread_mutex_lock(&display->draw_callbacks_mutex);
diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h
index f7f7e3468..356f69798 100644
--- a/libobs/obs-internal.h
+++ b/libobs/obs-internal.h
@@ -204,6 +204,7 @@ extern void obs_view_free(struct obs_view *view);
/* displays */
struct obs_display {
+ bool update_color_space;
bool enabled;
uint32_t cx, cy;
uint32_t next_cx, next_cy;
@@ -255,6 +256,7 @@ struct obs_core_video {
#endif
gs_texture_t *render_texture;
gs_texture_t *output_texture;
+ enum gs_color_space render_space;
bool texture_rendered;
bool textures_copied[NUM_TEXTURES];
bool texture_converted;
@@ -321,6 +323,7 @@ struct obs_core_video {
gs_effect_t *deinterlace_yadif_2x_effect;
struct obs_video_info ovi;
+ uint32_t sdr_white_level;
pthread_mutex_t task_mutex;
struct circlebuf tasks;
diff --git a/libobs/obs-video.c b/libobs/obs-video.c
index a966ed0d5..3b5343d72 100644
--- a/libobs/obs-video.c
+++ b/libobs/obs-video.c
@@ -133,7 +133,8 @@ static inline void render_main_texture(struct obs_core_video *video)
struct vec4 clear_color;
vec4_set(&clear_color, 0.0f, 0.0f, 0.0f, 0.0f);
- gs_set_render_target(video->render_texture, NULL);
+ gs_set_render_target_with_color_space(video->render_texture, NULL,
+ video->render_space);
gs_clear(GS_CLEAR_COLOR, &clear_color, 1.0f, 0);
set_render_size(video->base_width, video->base_height);
diff --git a/libobs/obs.c b/libobs/obs.c
index 7f5fd25bb..fd4efbd23 100644
--- a/libobs/obs.c
+++ b/libobs/obs.c
@@ -268,19 +268,24 @@ static bool obs_init_textures(struct obs_video_info *ovi)
}
}
+ enum gs_color_format format = GS_RGBA;
+ enum gs_color_space space = GS_CS_SRGB;
+
video->render_texture = gs_texture_create(ovi->base_width,
- ovi->base_height, GS_RGBA, 1,
+ ovi->base_height, format, 1,
NULL, GS_RENDER_TARGET);
if (!video->render_texture)
success = false;
video->output_texture = gs_texture_create(ovi->output_width,
- ovi->output_height, GS_RGBA,
- 1, NULL, GS_RENDER_TARGET);
+ ovi->output_height, format, 1,
+ NULL, GS_RENDER_TARGET);
if (!video->output_texture)
success = false;
- if (!success) {
+ if (success) {
+ video->render_space = space;
+ } else {
for (size_t i = 0; i < NUM_TEXTURES; i++) {
for (size_t c = 0; c < NUM_CHANNELS; c++) {
if (video->copy_surfaces[i][c]) {
@@ -1301,6 +1306,20 @@ bool obs_get_video_info(struct obs_video_info *ovi)
return true;
}
+float obs_get_video_sdr_white_level(void)
+{
+ struct obs_core_video *video = &obs->video;
+ return video->graphics ? video->sdr_white_level : 300.f;
+}
+
+void obs_set_video_sdr_white_level(float sdr_white_level)
+{
+ struct obs_core_video *video = &obs->video;
+ assert(video->graphics);
+
+ video->sdr_white_level = (uint32_t)sdr_white_level;
+}
+
bool obs_get_audio_info(struct obs_audio_info *oai)
{
struct obs_core_audio *audio = &obs->audio;
@@ -1793,18 +1812,37 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c,
if (!video->texture_rendered)
return;
+ const enum gs_color_space source_space = video->render_space;
+ const enum gs_color_space current_space = gs_get_color_space();
+ const char *tech_name = "Draw";
+ float multiplier = 1.0f;
+ if ((current_space == GS_CS_SRGB) &&
+ (source_space == GS_CS_709_EXTENDED)) {
+ tech_name = "DrawTonemap";
+ } else if (current_space == GS_CS_709_SCRGB) {
+ tech_name = "DrawMultiply";
+ multiplier = obs_get_video_sdr_white_level() / 80.0f;
+ }
+
+ const bool previous = gs_framebuffer_srgb_enabled();
+ gs_enable_framebuffer_srgb(true);
+
tex = video->render_texture;
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
param = gs_effect_get_param_by_name(effect, "image");
- gs_effect_set_texture(param, tex);
+ gs_effect_set_texture_srgb(param, tex);
+ param = gs_effect_get_param_by_name(effect, "multiplier");
+ gs_effect_set_float(param, multiplier);
gs_blend_state_push();
gs_blend_function_separate(src_c, dest_c, src_a, dest_a);
- while (gs_effect_loop(effect, "Draw"))
+ while (gs_effect_loop(effect, tech_name))
gs_draw_sprite(tex, 0, 0, 0);
gs_blend_state_pop();
+
+ gs_enable_framebuffer_srgb(previous);
}
void obs_render_main_texture(void)
diff --git a/libobs/obs.h b/libobs/obs.h
index 763ede1d4..5d5d66177 100644
--- a/libobs/obs.h
+++ b/libobs/obs.h
@@ -410,6 +410,12 @@ EXPORT bool obs_reset_audio(const struct obs_audio_info *oai);
/** Gets the current video settings, returns false if no video */
EXPORT bool obs_get_video_info(struct obs_video_info *ovi);
+/** Gets the SDR white level, returns 300.0 if no video */
+EXPORT float obs_get_video_sdr_white_level(void);
+
+/** Sets the SDR white level */
+EXPORT void obs_set_video_sdr_white_level(float sdr_white_level);
+
/** Gets the current audio settings, returns false if no audio */
EXPORT bool obs_get_audio_info(struct obs_audio_info *oai);
@@ -881,6 +887,9 @@ EXPORT void obs_display_destroy(obs_display_t *display);
EXPORT void obs_display_resize(obs_display_t *display, uint32_t cx,
uint32_t cy);
+/** Updates the color space of this display */
+EXPORT void obs_display_update_color_space(obs_display_t *display);
+
/**
* Adds a draw callback for this display context
*