diff --git a/UI/window-basic-filters.cpp b/UI/window-basic-filters.cpp index d27e59fba..52c2c2876 100644 --- a/UI/window-basic-filters.cpp +++ b/UI/window-basic-filters.cpp @@ -589,11 +589,13 @@ void OBSBasicFilters::DrawPreview(void *data, uint32_t cx, uint32_t cy) gs_viewport_push(); gs_projection_push(); + const bool previous = gs_set_linear_srgb(true); + gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); gs_set_viewport(x, y, newCX, newCY); - obs_source_video_render(window->source); + gs_set_linear_srgb(previous); gs_projection_pop(); gs_viewport_pop(); } diff --git a/UI/window-basic-interaction.cpp b/UI/window-basic-interaction.cpp index ed3519c86..304fb56c5 100644 --- a/UI/window-basic-interaction.cpp +++ b/UI/window-basic-interaction.cpp @@ -148,10 +148,13 @@ void OBSBasicInteraction::DrawPreview(void *data, uint32_t cx, uint32_t cy) gs_viewport_push(); gs_projection_push(); + const bool previous = gs_set_linear_srgb(true); + gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); gs_set_viewport(x, y, newCX, newCY); obs_source_video_render(window->source); + gs_set_linear_srgb(previous); gs_projection_pop(); gs_viewport_pop(); } diff --git a/UI/window-basic-properties.cpp b/UI/window-basic-properties.cpp index 52dd19fad..4f149667d 100644 --- a/UI/window-basic-properties.cpp +++ b/UI/window-basic-properties.cpp @@ -392,11 +392,13 @@ void OBSBasicProperties::DrawPreview(void *data, uint32_t cx, uint32_t cy) gs_viewport_push(); gs_projection_push(); + const bool previous = gs_set_linear_srgb(true); + gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); gs_set_viewport(x, y, newCX, newCY); - obs_source_video_render(window->source); + gs_set_linear_srgb(previous); gs_projection_pop(); gs_viewport_pop(); } diff --git a/docs/sphinx/graphics.rst b/docs/sphinx/graphics.rst index c2c91849a..8848a4653 100644 --- a/docs/sphinx/graphics.rst +++ b/docs/sphinx/graphics.rst @@ -77,6 +77,7 @@ Then the uniforms are set through the following functions: - :c:func:`gs_effect_set_vec3()` - :c:func:`gs_effect_set_vec4()` - :c:func:`gs_effect_set_texture()` +- :c:func:`gs_effect_set_texture_srgb()` There are two "universal" effect parameters that may be expected of effects: **ViewProj**, and **image**. The **ViewProj** parameter diff --git a/docs/sphinx/reference-libobs-graphics-effects.rst b/docs/sphinx/reference-libobs-graphics-effects.rst index e7c0ec389..b6d2f48b0 100644 --- a/docs/sphinx/reference-libobs-graphics-effects.rst +++ b/docs/sphinx/reference-libobs-graphics-effects.rst @@ -337,6 +337,15 @@ HLSL format. --------------------- +.. function:: void gs_effect_set_texture_srgb(gs_eparam_t *param, gs_texture_t *val) + + Sets a texture parameter using SRGB view if available. + + :param param: Effect parameter + :param val: Texture + +--------------------- + .. function:: void gs_effect_set_val(gs_eparam_t *param, const void *val, size_t size) Sets a parameter with data manually. diff --git a/docs/sphinx/reference-libobs-graphics-graphics.rst b/docs/sphinx/reference-libobs-graphics-graphics.rst index 69a60d8ca..6edf34efb 100644 --- a/docs/sphinx/reference-libobs-graphics-graphics.rst +++ b/docs/sphinx/reference-libobs-graphics-graphics.rst @@ -42,6 +42,9 @@ Graphics Enumerations - GS_DXT1 - Compressed DXT1 - GS_DXT3 - Compressed DXT3 - GS_DXT5 - Compressed DXT5 + - GS_RGBA_UNORM - RGBA, 8 bits per channel, no SRGB aliasing + - GS_BGRX_UNORM - BGRX, 8 bits per channel, no SRGB aliasing + - GS_BGRA_UNORM - BGRA, 8 bits per channel, no SRGB aliasing .. type:: enum gs_zstencil_format diff --git a/libobs-d3d11/d3d11-rebuild.cpp b/libobs-d3d11/d3d11-rebuild.cpp index 113f711b4..5924adb33 100644 --- a/libobs-d3d11/d3d11-rebuild.cpp +++ b/libobs-d3d11/d3d11-rebuild.cpp @@ -33,24 +33,37 @@ void gs_index_buffer::Rebuild(ID3D11Device *dev) void gs_texture_2d::RebuildSharedTextureFallback() { + static const gs_color_format format = GS_BGRA; + static const DXGI_FORMAT dxgi_format_resource = + ConvertGSTextureFormatResource(format); + static const DXGI_FORMAT dxgi_format_view = + ConvertGSTextureFormatView(format); + static const DXGI_FORMAT dxgi_format_view_linear = + ConvertGSTextureFormatViewLinear(format); + td = {}; td.Width = 2; td.Height = 2; td.MipLevels = 1; - td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + td.Format = dxgi_format_resource; td.ArraySize = 1; td.SampleDesc.Count = 1; td.BindFlags = D3D11_BIND_SHADER_RESOURCE; width = td.Width; height = td.Height; - dxgiFormat = td.Format; + dxgiFormatResource = dxgi_format_resource; + dxgiFormatView = dxgi_format_view; + dxgiFormatViewLinear = dxgi_format_view_linear; levels = 1; - resourceDesc = {}; - resourceDesc.Format = td.Format; - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - resourceDesc.Texture2D.MipLevels = 1; + viewDesc = {}; + viewDesc.Format = dxgi_format_view; + viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + viewDesc.Texture2D.MipLevels = 1; + + viewDescLinear = viewDesc; + viewDescLinear.Format = dxgi_format_view_linear; isShared = false; } @@ -77,9 +90,18 @@ void gs_texture_2d::Rebuild(ID3D11Device *dev) throw HRError("Failed to create 2D texture", hr); } - hr = dev->CreateShaderResourceView(texture, &resourceDesc, &shaderRes); + hr = dev->CreateShaderResourceView(texture, &viewDesc, &shaderRes); if (FAILED(hr)) - throw HRError("Failed to create resource view", hr); + throw HRError("Failed to create SRV", hr); + + if (viewDesc.Format == viewDescLinear.Format) { + shaderResLinear = shaderRes; + } else { + hr = dev->CreateShaderResourceView(texture, &viewDescLinear, + &shaderResLinear); + if (FAILED(hr)) + throw HRError("Failed to create linear SRV", hr); + } if (isRenderTarget) InitRenderTargets(); @@ -110,9 +132,18 @@ void gs_texture_2d::RebuildNV12_Y(ID3D11Device *dev) if (FAILED(hr)) throw HRError("Failed to create 2D texture", hr); - hr = dev->CreateShaderResourceView(texture, &resourceDesc, &shaderRes); + hr = dev->CreateShaderResourceView(texture, &viewDesc, &shaderRes); if (FAILED(hr)) - throw HRError("Failed to create resource view", hr); + throw HRError("Failed to create Y SRV", hr); + + if (viewDesc.Format == viewDescLinear.Format) { + shaderResLinear = shaderRes; + } else { + hr = dev->CreateShaderResourceView(texture, &viewDescLinear, + &shaderResLinear); + if (FAILED(hr)) + throw HRError("Failed to create linear Y SRV", hr); + } if (isRenderTarget) InitRenderTargets(); @@ -136,9 +167,18 @@ void gs_texture_2d::RebuildNV12_UV(ID3D11Device *dev) texture = tex_y->texture; - hr = dev->CreateShaderResourceView(texture, &resourceDesc, &shaderRes); + hr = dev->CreateShaderResourceView(texture, &viewDesc, &shaderRes); if (FAILED(hr)) - throw HRError("Failed to create resource view", hr); + throw HRError("Failed to create UV SRV", hr); + + if (viewDesc.Format == viewDescLinear.Format) { + shaderResLinear = shaderRes; + } else { + hr = dev->CreateShaderResourceView(texture, &viewDescLinear, + &shaderResLinear); + if (FAILED(hr)) + throw HRError("Failed to create linear UV SRV", hr); + } if (isRenderTarget) InitRenderTargets(); @@ -253,25 +293,38 @@ void gs_timer_range::Rebuild(ID3D11Device *dev) void gs_texture_3d::RebuildSharedTextureFallback() { + static const gs_color_format format = GS_BGRA; + static const DXGI_FORMAT dxgi_format_resource = + ConvertGSTextureFormatResource(format); + static const DXGI_FORMAT dxgi_format_view = + ConvertGSTextureFormatView(format); + static const DXGI_FORMAT dxgi_format_view_linear = + ConvertGSTextureFormatViewLinear(format); + td = {}; td.Width = 2; td.Height = 2; td.Depth = 2; td.MipLevels = 1; - td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + td.Format = dxgi_format_resource; td.BindFlags = D3D11_BIND_SHADER_RESOURCE; width = td.Width; height = td.Height; depth = td.Depth; - dxgiFormat = td.Format; + dxgiFormatResource = dxgi_format_resource; + dxgiFormatView = dxgi_format_view; + dxgiFormatViewLinear = dxgi_format_view_linear; levels = 1; - resourceDesc = {}; - resourceDesc.Format = td.Format; - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; - resourceDesc.Texture3D.MostDetailedMip = 0; - resourceDesc.Texture3D.MipLevels = 1; + viewDesc = {}; + viewDesc.Format = dxgi_format_view; + viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + viewDesc.Texture3D.MostDetailedMip = 0; + viewDesc.Texture3D.MipLevels = 1; + + viewDescLinear = viewDesc; + viewDescLinear.Format = dxgi_format_view_linear; isShared = false; } @@ -298,9 +351,18 @@ void gs_texture_3d::Rebuild(ID3D11Device *dev) throw HRError("Failed to create 3D texture", hr); } - hr = dev->CreateShaderResourceView(texture, &resourceDesc, &shaderRes); + hr = dev->CreateShaderResourceView(texture, &viewDesc, &shaderRes); if (FAILED(hr)) - throw HRError("Failed to create resource view", hr); + throw HRError("Failed to create 3D SRV", hr); + + if (viewDesc.Format == viewDescLinear.Format) { + shaderResLinear = shaderRes; + } else { + hr = dev->CreateShaderResourceView(texture, &viewDescLinear, + &shaderResLinear); + if (FAILED(hr)) + throw HRError("Failed to create linear 3D SRV", hr); + } acquired = false; diff --git a/libobs-d3d11/d3d11-shader.cpp b/libobs-d3d11/d3d11-shader.cpp index 6260ef819..66bb7b3d3 100644 --- a/libobs-d3d11/d3d11-shader.cpp +++ b/libobs-d3d11/d3d11-shader.cpp @@ -262,10 +262,15 @@ inline void gs_shader::UpdateParam(vector &constData, param.changed = false; } - } else if (param.curValue.size() == sizeof(gs_texture_t *)) { - gs_texture_t *tex; - memcpy(&tex, param.curValue.data(), sizeof(gs_texture_t *)); - device_load_texture(device, tex, param.textureID); + } else if (param.curValue.size() == sizeof(struct gs_shader_texture)) { + struct gs_shader_texture shader_tex; + memcpy(&shader_tex, param.curValue.data(), sizeof(shader_tex)); + if (shader_tex.srgb) + device_load_texture_srgb(device, shader_tex.tex, + param.textureID); + else + device_load_texture(device, shader_tex.tex, + param.textureID); if (param.nextSampler) { ID3D11SamplerState *state = param.nextSampler->state; diff --git a/libobs-d3d11/d3d11-stagesurf.cpp b/libobs-d3d11/d3d11-stagesurf.cpp index 84e9eef51..00517527a 100644 --- a/libobs-d3d11/d3d11-stagesurf.cpp +++ b/libobs-d3d11/d3d11-stagesurf.cpp @@ -23,7 +23,7 @@ gs_stage_surface::gs_stage_surface(gs_device_t *device, uint32_t width, width(width), height(height), format(colorFormat), - dxgiFormat(ConvertGSTextureFormat(colorFormat)) + dxgiFormat(ConvertGSTextureFormatView(colorFormat)) { HRESULT hr; diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp index dce761aff..bc9898758 100644 --- a/libobs-d3d11/d3d11-subsystem.cpp +++ b/libobs-d3d11/d3d11-subsystem.cpp @@ -78,7 +78,7 @@ static inline void make_swap_desc(DXGI_SWAP_CHAIN_DESC &desc, { memset(&desc, 0, sizeof(desc)); desc.BufferCount = data->num_backbuffers; - desc.BufferDesc.Format = ConvertGSTextureFormat(data->format); + desc.BufferDesc.Format = ConvertGSTextureFormatView(data->format); desc.BufferDesc.Width = data->cx; desc.BufferDesc.Height = data->cy; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; @@ -99,10 +99,24 @@ void gs_swap_chain::InitTarget(uint32_t cx, uint32_t cy) if (FAILED(hr)) throw HRError("Failed to get swap buffer texture", hr); + D3D11_RENDER_TARGET_VIEW_DESC rtv; + rtv.Format = target.dxgiFormatView; + rtv.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + rtv.Texture2D.MipSlice = 0; hr = device->device->CreateRenderTargetView( - target.texture, NULL, target.renderTarget[0].Assign()); + target.texture, &rtv, target.renderTarget[0].Assign()); if (FAILED(hr)) - throw HRError("Failed to create swap render target view", hr); + throw HRError("Failed to create swap RTV", hr); + if (target.dxgiFormatView == target.dxgiFormatViewLinear) { + target.renderTargetLinear[0] = target.renderTarget[0]; + } else { + rtv.Format = target.dxgiFormatViewLinear; + hr = device->device->CreateRenderTargetView( + target.texture, &rtv, + target.renderTargetLinear[0].Assign()); + if (FAILED(hr)) + throw HRError("Failed to create linear swap RTV", hr); + } } void gs_swap_chain::InitZStencilBuffer(uint32_t cx, uint32_t cy) @@ -125,6 +139,7 @@ void gs_swap_chain::Resize(uint32_t cx, uint32_t cy) target.texture.Clear(); target.renderTarget[0].Clear(); + target.renderTargetLinear[0].Clear(); zs.texture.Clear(); zs.view.Clear(); @@ -139,7 +154,7 @@ void gs_swap_chain::Resize(uint32_t cx, uint32_t cy) cy = clientRect.bottom; } - hr = swap->ResizeBuffers(numBuffers, cx, cy, target.dxgiFormat, 0); + hr = swap->ResizeBuffers(numBuffers, cx, cy, DXGI_FORMAT_UNKNOWN, 0); if (FAILED(hr)) throw HRError("Failed to resize swap buffers", hr); @@ -152,7 +167,11 @@ void gs_swap_chain::Init() target.device = device; target.isRenderTarget = true; target.format = initData.format; - target.dxgiFormat = ConvertGSTextureFormat(initData.format); + target.dxgiFormatResource = + ConvertGSTextureFormatResource(initData.format); + target.dxgiFormatView = ConvertGSTextureFormatView(initData.format); + target.dxgiFormatViewLinear = + ConvertGSTextureFormatViewLinear(initData.format); InitTarget(initData.cx, initData.cy); zs.device = device; @@ -310,6 +329,7 @@ try { UpdateBlendState(); UpdateRasterState(); UpdateZStencilState(); + FlushOutputViews(); context->Draw(4, 0); device_set_viewport(this, 0, 0, NV12_CX / 2, NV12_CY / 2); @@ -318,6 +338,7 @@ try { UpdateBlendState(); UpdateRasterState(); UpdateZStencilState(); + FlushOutputViews(); context->Draw(4, 0); device_load_pixelshader(this, nullptr); @@ -727,6 +748,30 @@ void gs_device::UpdateViewProjMatrix() &curViewProjMatrix); } +void gs_device::FlushOutputViews() +{ + if (curFramebufferInvalidate) { + ID3D11RenderTargetView *rtv = nullptr; + if (curRenderTarget) { + const int i = curRenderSide; + rtv = curFramebufferSrgb + ? curRenderTarget->renderTargetLinear[i] + .Get() + : curRenderTarget->renderTarget[i].Get(); + if (!rtv) { + blog(LOG_ERROR, + "device_draw (D3D11): texture is not a render target"); + return; + } + } + ID3D11DepthStencilView *dsv = nullptr; + if (curZStencilBuffer) + dsv = curZStencilBuffer->view; + context->OMSetRenderTargets(1, &rtv, dsv); + curFramebufferInvalidate = false; + } +} + gs_device::gs_device(uint32_t adapterIdx) : curToplogy(D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED) { @@ -1045,18 +1090,9 @@ void device_resize(gs_device_t *device, uint32_t cx, uint32_t cy) try { ID3D11RenderTargetView *renderView = NULL; - ID3D11DepthStencilView *depthView = NULL; - int i = device->curRenderSide; - - device->context->OMSetRenderTargets(1, &renderView, depthView); + device->context->OMSetRenderTargets(1, &renderView, NULL); device->curSwapChain->Resize(cx, cy); - - if (device->curRenderTarget) - renderView = device->curRenderTarget->renderTarget[i]; - if (device->curZStencilBuffer) - depthView = device->curZStencilBuffer->view; - device->context->OMSetRenderTargets(1, &renderView, depthView); - + device->curFramebufferInvalidate = true; } catch (const HRError &error) { blog(LOG_ERROR, "device_resize (D3D11): %s (%08lX)", error.str, error.hr); @@ -1419,20 +1455,29 @@ void device_load_indexbuffer(gs_device_t *device, gs_indexbuffer_t *indexbuffer) device->context->IASetIndexBuffer(buffer, format, 0); } -void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) +static void device_load_texture_internal(gs_device_t *device, gs_texture_t *tex, + int unit, + ID3D11ShaderResourceView *view) { - ID3D11ShaderResourceView *view = NULL; - if (device->curTextures[unit] == tex) return; - if (tex) - view = tex->shaderRes; - device->curTextures[unit] = tex; device->context->PSSetShaderResources(unit, 1, &view); } +void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) +{ + ID3D11ShaderResourceView *view = tex ? tex->shaderRes : NULL; + return device_load_texture_internal(device, tex, unit, view); +} + +void device_load_texture_srgb(gs_device_t *device, gs_texture_t *tex, int unit) +{ + ID3D11ShaderResourceView *view = tex ? tex->shaderResLinear : NULL; + return device_load_texture_internal(device, tex, unit, view); +} + void device_load_samplerstate(gs_device_t *device, gs_samplerstate_t *samplerstate, int unit) { @@ -1574,26 +1619,19 @@ void device_set_render_target(gs_device_t *device, gs_texture_t *tex, return; if (tex && tex->type != GS_TEXTURE_2D) { - blog(LOG_ERROR, "device_set_render_target (D3D11): " - "texture is not a 2D texture"); + blog(LOG_ERROR, + "device_set_render_target (D3D11): texture is not a 2D texture"); return; } - gs_texture_2d *tex2d = static_cast(tex); - if (tex2d && !tex2d->renderTarget[0]) { - blog(LOG_ERROR, "device_set_render_target (D3D11): " - "texture is not a render target"); - return; + gs_texture_2d *const tex2d = static_cast(tex); + if (device->curRenderTarget != tex2d || device->curRenderSide != 0 || + device->curZStencilBuffer != zstencil) { + device->curRenderTarget = tex2d; + device->curRenderSide = 0; + device->curZStencilBuffer = zstencil; + device->curFramebufferInvalidate = true; } - - ID3D11RenderTargetView *rt = tex2d ? tex2d->renderTarget[0].Get() - : nullptr; - - device->curRenderTarget = tex2d; - device->curRenderSide = 0; - device->curZStencilBuffer = zstencil; - device->context->OMSetRenderTargets( - 1, &rt, zstencil ? zstencil->view : nullptr); } void device_set_cube_render_target(gs_device_t *device, gs_texture_t *tex, @@ -1619,19 +1657,27 @@ void device_set_cube_render_target(gs_device_t *device, gs_texture_t *tex, return; } - gs_texture_2d *tex2d = static_cast(tex); - if (!tex2d->renderTarget[side]) { - blog(LOG_ERROR, "device_set_cube_render_target (D3D11): " - "texture is not a render target"); - return; + gs_texture_2d *const tex2d = static_cast(tex); + if (device->curRenderTarget != tex2d || device->curRenderSide != side || + device->curZStencilBuffer != zstencil) { + device->curRenderTarget = tex2d; + device->curRenderSide = side; + device->curZStencilBuffer = zstencil; + device->curFramebufferInvalidate = true; } +} - ID3D11RenderTargetView *rt = tex2d->renderTarget[0]; +void device_enable_framebuffer_srgb(gs_device_t *device, bool enable) +{ + if (device->curFramebufferSrgb != enable) { + device->curFramebufferSrgb = enable; + device->curFramebufferInvalidate = true; + } +} - device->curRenderTarget = tex2d; - device->curRenderSide = side; - device->curZStencilBuffer = zstencil; - device->context->OMSetRenderTargets(1, &rt, zstencil->view); +bool device_framebuffer_srgb_enabled(gs_device_t *device) +{ + return device->curFramebufferSrgb; } inline void gs_device::CopyTex(ID3D11Texture2D *dst, uint32_t dst_x, @@ -1780,6 +1826,8 @@ void device_draw(gs_device_t *device, enum gs_draw_mode draw_mode, if (!device->curSwapChain && !device->curRenderTarget) throw "No render target or swap chain to render to"; + device->FlushOutputViews(); + gs_effect_t *effect = gs_get_effect(); if (effect) gs_effect_update_params(effect); @@ -2589,9 +2637,10 @@ device_texture_create_gdi(gs_device_t *device, uint32_t width, uint32_t height) { gs_texture *texture = nullptr; try { - texture = new gs_texture_2d(device, width, height, GS_BGRA, 1, - nullptr, GS_RENDER_TARGET, - GS_TEXTURE_2D, true); + texture = new gs_texture_2d(device, width, height, + GS_BGRA_UNORM, 1, nullptr, + GS_RENDER_TARGET, GS_TEXTURE_2D, + true); } catch (const HRError &error) { blog(LOG_ERROR, "device_texture_create_gdi (D3D11): %s (%08lX)", error.str, error.hr); diff --git a/libobs-d3d11/d3d11-subsystem.hpp b/libobs-d3d11/d3d11-subsystem.hpp index 0fee8b0aa..0f03ddd5d 100644 --- a/libobs-d3d11/d3d11-subsystem.hpp +++ b/libobs-d3d11/d3d11-subsystem.hpp @@ -59,7 +59,7 @@ static inline uint32_t GetWinVer() return (ver.major << 8) | ver.minor; } -static inline DXGI_FORMAT ConvertGSTextureFormat(gs_color_format format) +static inline DXGI_FORMAT ConvertGSTextureFormatResource(gs_color_format format) { switch (format) { case GS_UNKNOWN: @@ -69,11 +69,11 @@ static inline DXGI_FORMAT ConvertGSTextureFormat(gs_color_format format) case GS_R8: return DXGI_FORMAT_R8_UNORM; case GS_RGBA: - return DXGI_FORMAT_R8G8B8A8_UNORM; + return DXGI_FORMAT_R8G8B8A8_TYPELESS; case GS_BGRX: - return DXGI_FORMAT_B8G8R8X8_UNORM; + return DXGI_FORMAT_B8G8R8X8_TYPELESS; case GS_BGRA: - return DXGI_FORMAT_B8G8R8A8_UNORM; + return DXGI_FORMAT_B8G8R8A8_TYPELESS; case GS_R10G10B10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; case GS_RGBA16: @@ -100,11 +100,46 @@ static inline DXGI_FORMAT ConvertGSTextureFormat(gs_color_format format) return DXGI_FORMAT_BC3_UNORM; case GS_R8G8: return DXGI_FORMAT_R8G8_UNORM; + case GS_RGBA_UNORM: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case GS_BGRX_UNORM: + return DXGI_FORMAT_B8G8R8X8_UNORM; + case GS_BGRA_UNORM: + return DXGI_FORMAT_B8G8R8A8_UNORM; } return DXGI_FORMAT_UNKNOWN; } +static inline DXGI_FORMAT ConvertGSTextureFormatView(gs_color_format format) +{ + switch (format) { + case GS_RGBA: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case GS_BGRX: + return DXGI_FORMAT_B8G8R8X8_UNORM; + case GS_BGRA: + return DXGI_FORMAT_B8G8R8A8_UNORM; + default: + return ConvertGSTextureFormatResource(format); + } +} + +static inline DXGI_FORMAT +ConvertGSTextureFormatViewLinear(gs_color_format format) +{ + switch (format) { + case GS_RGBA: + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case GS_BGRX: + return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; + case GS_BGRA: + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + default: + return ConvertGSTextureFormatResource(format); + } +} + static inline gs_color_format ConvertDXGITextureFormat(DXGI_FORMAT format) { switch ((unsigned long)format) { @@ -115,12 +150,9 @@ static inline gs_color_format ConvertDXGITextureFormat(DXGI_FORMAT format) case DXGI_FORMAT_R8G8_UNORM: return GS_R8G8; case DXGI_FORMAT_R8G8B8A8_TYPELESS: - case DXGI_FORMAT_R8G8B8A8_UNORM: return GS_RGBA; - case DXGI_FORMAT_B8G8R8X8_UNORM: case DXGI_FORMAT_B8G8R8X8_TYPELESS: return GS_BGRX; - case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: return GS_BGRA; case DXGI_FORMAT_R10G10B10A2_UNORM: @@ -147,6 +179,12 @@ static inline gs_color_format ConvertDXGITextureFormat(DXGI_FORMAT format) return GS_DXT3; case DXGI_FORMAT_BC3_UNORM: return GS_DXT5; + case DXGI_FORMAT_R8G8B8A8_UNORM: + return GS_RGBA_UNORM; + case DXGI_FORMAT_B8G8R8X8_UNORM: + return GS_BGRX_UNORM; + case DXGI_FORMAT_B8G8R8A8_UNORM: + return GS_BGRA_UNORM; } return GS_UNKNOWN; @@ -410,7 +448,9 @@ struct gs_texture : gs_obj { gs_color_format format; ComPtr shaderRes; - D3D11_SHADER_RESOURCE_VIEW_DESC resourceDesc = {}; + ComPtr shaderResLinear; + D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc{}; + D3D11_SHADER_RESOURCE_VIEW_DESC viewDescLinear{}; void Rebuild(ID3D11Device *dev); @@ -440,11 +480,14 @@ struct gs_texture : gs_obj { struct gs_texture_2d : gs_texture { ComPtr texture; ComPtr renderTarget[6]; + ComPtr renderTargetLinear[6]; ComPtr gdiSurface; uint32_t width = 0, height = 0; uint32_t flags = 0; - DXGI_FORMAT dxgiFormat = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT dxgiFormatResource = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT dxgiFormatView = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT dxgiFormatViewLinear = DXGI_FORMAT_UNKNOWN; bool isRenderTarget = false; bool isGDICompatible = false; bool isDynamic = false; @@ -476,10 +519,13 @@ struct gs_texture_2d : gs_texture { inline void Release() { texture.Release(); - for (auto &rt : renderTarget) + for (ComPtr &rt : renderTarget) + rt.Release(); + for (ComPtr &rt : renderTargetLinear) rt.Release(); gdiSurface.Release(); shaderRes.Release(); + shaderResLinear.Release(); } inline gs_texture_2d() : gs_texture(GS_TEXTURE_2D, 0, GS_UNKNOWN) {} @@ -501,7 +547,9 @@ struct gs_texture_3d : gs_texture { uint32_t width = 0, height = 0, depth = 0; uint32_t flags = 0; - DXGI_FORMAT dxgiFormat = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT dxgiFormatResource = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT dxgiFormatView = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT dxgiFormatViewLinear = DXGI_FORMAT_UNKNOWN; bool isDynamic = false; bool isShared = false; bool genMipmaps = false; @@ -912,6 +960,8 @@ struct gs_device { gs_texture_2d *curRenderTarget = nullptr; gs_zstencil_buffer *curZStencilBuffer = nullptr; int curRenderSide = 0; + bool curFramebufferSrgb = false; + bool curFramebufferInvalidate = false; gs_texture *curTextures[GS_MAX_TEXTURES]; gs_sampler_state *curSamplers[GS_MAX_TEXTURES]; gs_vertex_buffer *curVertexBuffer = nullptr; @@ -972,6 +1022,8 @@ struct gs_device { void UpdateViewProjMatrix(); + void FlushOutputViews(); + void RebuildDevice(); bool HasBadNV12Output(); diff --git a/libobs-d3d11/d3d11-texture2d.cpp b/libobs-d3d11/d3d11-texture2d.cpp index 05b4b9189..58bd7dc80 100644 --- a/libobs-d3d11/d3d11-texture2d.cpp +++ b/libobs-d3d11/d3d11-texture2d.cpp @@ -98,7 +98,7 @@ void gs_texture_2d::InitTexture(const uint8_t *const *data) td.Height = height; td.MipLevels = genMipmaps ? 0 : levels; td.ArraySize = type == GS_TEXTURE_CUBE ? 6 : 1; - td.Format = nv12 ? DXGI_FORMAT_NV12 : dxgiFormat; + td.Format = nv12 ? DXGI_FORMAT_NV12 : dxgiFormatResource; td.BindFlags = D3D11_BIND_SHADER_RESOURCE; td.SampleDesc.Count = 1; td.CPUAccessFlags = isDynamic ? D3D11_CPU_ACCESS_WRITE : 0; @@ -172,23 +172,35 @@ void gs_texture_2d::InitResourceView() { HRESULT hr; - memset(&resourceDesc, 0, sizeof(resourceDesc)); - resourceDesc.Format = dxgiFormat; + memset(&viewDesc, 0, sizeof(viewDesc)); + viewDesc.Format = dxgiFormatView; if (type == GS_TEXTURE_CUBE) { - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; - resourceDesc.TextureCube.MipLevels = - genMipmaps || !levels ? -1 : levels; + viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; + viewDesc.TextureCube.MipLevels = genMipmaps || !levels ? -1 + : levels; } else { - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - resourceDesc.Texture2D.MipLevels = - genMipmaps || !levels ? -1 : levels; + viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + viewDesc.Texture2D.MipLevels = genMipmaps || !levels ? -1 + : levels; } - hr = device->device->CreateShaderResourceView(texture, &resourceDesc, + hr = device->device->CreateShaderResourceView(texture, &viewDesc, shaderRes.Assign()); if (FAILED(hr)) - throw HRError("Failed to create resource view", hr); + throw HRError("Failed to create SRV", hr); + + viewDescLinear = viewDesc; + viewDescLinear.Format = dxgiFormatViewLinear; + + if (dxgiFormatView == dxgiFormatViewLinear) { + shaderResLinear = shaderRes; + } else { + hr = device->device->CreateShaderResourceView( + texture, &viewDescLinear, shaderResLinear.Assign()); + if (FAILED(hr)) + throw HRError("Failed to create linear SRV", hr); + } } void gs_texture_2d::InitRenderTargets() @@ -196,18 +208,27 @@ void gs_texture_2d::InitRenderTargets() HRESULT hr; if (type == GS_TEXTURE_2D) { D3D11_RENDER_TARGET_VIEW_DESC rtv; - rtv.Format = dxgiFormat; + rtv.Format = dxgiFormatView; rtv.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; rtv.Texture2D.MipSlice = 0; hr = device->device->CreateRenderTargetView( texture, &rtv, renderTarget[0].Assign()); if (FAILED(hr)) - throw HRError("Failed to create render target view", - hr); + throw HRError("Failed to create RTV", hr); + if (dxgiFormatView == dxgiFormatViewLinear) { + renderTargetLinear[0] = renderTarget[0]; + } else { + rtv.Format = dxgiFormatViewLinear; + hr = device->device->CreateRenderTargetView( + texture, &rtv, renderTargetLinear[0].Assign()); + if (FAILED(hr)) + throw HRError("Failed to create linear RTV", + hr); + } } else { D3D11_RENDER_TARGET_VIEW_DESC rtv; - rtv.Format = dxgiFormat; + rtv.Format = dxgiFormatView; rtv.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; rtv.Texture2DArray.MipSlice = 0; rtv.Texture2DArray.ArraySize = 1; @@ -217,9 +238,19 @@ void gs_texture_2d::InitRenderTargets() hr = device->device->CreateRenderTargetView( texture, &rtv, renderTarget[i].Assign()); if (FAILED(hr)) - throw HRError("Failed to create cube render " - "target view", - hr); + throw HRError("Failed to create cube RTV", hr); + if (dxgiFormatView == dxgiFormatViewLinear) { + renderTargetLinear[i] = renderTarget[i]; + } else { + rtv.Format = dxgiFormatViewLinear; + hr = device->device->CreateRenderTargetView( + texture, &rtv, + renderTargetLinear[i].Assign()); + if (FAILED(hr)) + throw HRError( + "Failed to create linear cube RTV", + hr); + } } } } @@ -235,7 +266,9 @@ gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t width, width(width), height(height), flags(flags_), - dxgiFormat(ConvertGSTextureFormat(format)), + dxgiFormatResource(ConvertGSTextureFormatResource(format)), + dxgiFormatView(ConvertGSTextureFormatView(format)), + dxgiFormatViewLinear(ConvertGSTextureFormatViewLinear(format)), isRenderTarget((flags_ & GS_RENDER_TARGET) != 0), isGDICompatible(gdiCompatible), isDynamic((flags_ & GS_DYNAMIC) != 0), @@ -271,7 +304,9 @@ gs_texture_2d::gs_texture_2d(gs_device_t *device, ID3D11Texture2D *nv12tex, this->chroma = true; this->width = td.Width / 2; this->height = td.Height / 2; - this->dxgiFormat = DXGI_FORMAT_R8G8_UNORM; + this->dxgiFormatResource = DXGI_FORMAT_R8G8_UNORM; + this->dxgiFormatView = DXGI_FORMAT_R8G8_UNORM; + this->dxgiFormatViewLinear = DXGI_FORMAT_R8G8_UNORM; InitResourceView(); if (isRenderTarget) @@ -292,24 +327,20 @@ gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t handle) texture->GetDesc(&td); + const gs_color_format format = ConvertDXGITextureFormat(td.Format); + this->type = GS_TEXTURE_2D; - this->format = ConvertDXGITextureFormat(td.Format); + this->format = format; this->levels = 1; this->device = device; this->width = td.Width; this->height = td.Height; - this->dxgiFormat = td.Format; + this->dxgiFormatResource = ConvertGSTextureFormatResource(format); + this->dxgiFormatView = ConvertGSTextureFormatView(format); + this->dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format); - memset(&resourceDesc, 0, sizeof(resourceDesc)); - resourceDesc.Format = ConvertGSTextureFormat(this->format); - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - resourceDesc.Texture2D.MipLevels = 1; - - hr = device->device->CreateShaderResourceView(texture, &resourceDesc, - shaderRes.Assign()); - if (FAILED(hr)) - throw HRError("Failed to create shader resource view", hr); + InitResourceView(); } gs_texture_2d::gs_texture_2d(gs_device_t *device, ID3D11Texture2D *obj) @@ -319,22 +350,18 @@ gs_texture_2d::gs_texture_2d(gs_device_t *device, ID3D11Texture2D *obj) texture->GetDesc(&td); + const gs_color_format format = ConvertDXGITextureFormat(td.Format); + this->type = GS_TEXTURE_2D; - this->format = ConvertDXGITextureFormat(td.Format); + this->format = format; this->levels = 1; this->device = device; this->width = td.Width; this->height = td.Height; - this->dxgiFormat = td.Format; + this->dxgiFormatResource = ConvertGSTextureFormatResource(format); + this->dxgiFormatView = ConvertGSTextureFormatView(format); + this->dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format); - memset(&resourceDesc, 0, sizeof(resourceDesc)); - resourceDesc.Format = ConvertGSTextureFormat(this->format); - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - resourceDesc.Texture2D.MipLevels = 1; - - HRESULT hr = device->device->CreateShaderResourceView( - texture, &resourceDesc, shaderRes.Assign()); - if (FAILED(hr)) - throw HRError("Failed to create shader resource view", hr); + InitResourceView(); } diff --git a/libobs-d3d11/d3d11-texture3d.cpp b/libobs-d3d11/d3d11-texture3d.cpp index 9afd25d26..6291b9cbf 100644 --- a/libobs-d3d11/d3d11-texture3d.cpp +++ b/libobs-d3d11/d3d11-texture3d.cpp @@ -95,7 +95,7 @@ void gs_texture_3d::InitTexture(const uint8_t *const *data) td.Height = height; td.Depth = depth; td.MipLevels = genMipmaps ? 0 : levels; - td.Format = dxgiFormat; + td.Format = dxgiFormatResource; td.BindFlags = D3D11_BIND_SHADER_RESOURCE; td.CPUAccessFlags = isDynamic ? D3D11_CPU_ACCESS_WRITE : 0; td.Usage = isDynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT; @@ -155,17 +155,29 @@ void gs_texture_3d::InitResourceView() { HRESULT hr; - memset(&resourceDesc, 0, sizeof(resourceDesc)); - resourceDesc.Format = dxgiFormat; + memset(&viewDesc, 0, sizeof(viewDesc)); + viewDesc.Format = dxgiFormatView; - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; - resourceDesc.Texture3D.MostDetailedMip = 0; - resourceDesc.Texture3D.MipLevels = genMipmaps || !levels ? -1 : levels; + viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + viewDesc.Texture3D.MostDetailedMip = 0; + viewDesc.Texture3D.MipLevels = genMipmaps || !levels ? -1 : levels; - hr = device->device->CreateShaderResourceView(texture, &resourceDesc, + hr = device->device->CreateShaderResourceView(texture, &viewDesc, shaderRes.Assign()); if (FAILED(hr)) - throw HRError("Failed to create resource view", hr); + throw HRError("Failed to create 3D SRV", hr); + + viewDescLinear = viewDesc; + viewDescLinear.Format = dxgiFormatViewLinear; + + if (dxgiFormatView == dxgiFormatViewLinear) { + shaderResLinear = shaderRes; + } else { + hr = device->device->CreateShaderResourceView( + texture, &viewDescLinear, shaderResLinear.Assign()); + if (FAILED(hr)) + throw HRError("Failed to create linear 3D SRV", hr); + } } #define SHARED_FLAGS (GS_SHARED_TEX | GS_SHARED_KM_TEX) @@ -180,7 +192,9 @@ gs_texture_3d::gs_texture_3d(gs_device_t *device, uint32_t width, height(height), depth(depth), flags(flags_), - dxgiFormat(ConvertGSTextureFormat(format)), + dxgiFormatResource(ConvertGSTextureFormatResource(format)), + dxgiFormatView(ConvertGSTextureFormatView(format)), + dxgiFormatViewLinear(ConvertGSTextureFormatViewLinear(format)), isDynamic((flags_ & GS_DYNAMIC) != 0), isShared((flags_ & SHARED_FLAGS) != 0), genMipmaps((flags_ & GS_BUILD_MIPMAPS) != 0), @@ -203,24 +217,19 @@ gs_texture_3d::gs_texture_3d(gs_device_t *device, uint32_t handle) texture->GetDesc(&td); + const gs_color_format format = ConvertDXGITextureFormat(td.Format); + this->type = GS_TEXTURE_3D; - this->format = ConvertDXGITextureFormat(td.Format); + this->format = format; this->levels = 1; this->device = device; this->width = td.Width; this->height = td.Height; this->depth = td.Depth; - this->dxgiFormat = td.Format; + this->dxgiFormatResource = ConvertGSTextureFormatResource(format); + this->dxgiFormatView = ConvertGSTextureFormatView(format); + this->dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format); - memset(&resourceDesc, 0, sizeof(resourceDesc)); - resourceDesc.Format = td.Format; - resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; - resourceDesc.Texture3D.MostDetailedMip = 0; - resourceDesc.Texture3D.MipLevels = 1; - - hr = device->device->CreateShaderResourceView(texture, &resourceDesc, - shaderRes.Assign()); - if (FAILED(hr)) - throw HRError("Failed to create shader resource view", hr); + InitResourceView(); } diff --git a/libobs-opengl/gl-shader.c b/libobs-opengl/gl-shader.c index a5cdbbb89..cc6eed808 100644 --- a/libobs-opengl/gl-shader.c +++ b/libobs-opengl/gl-shader.c @@ -525,8 +525,13 @@ static void program_set_param_data(struct gs_program *program, } glUniform1i(pp->obj, pp->param->texture_id); - device_load_texture(program->device, pp->param->texture, - pp->param->texture_id); + if (pp->param->srgb) + device_load_texture_srgb(program->device, + pp->param->texture, + pp->param->texture_id); + else + device_load_texture(program->device, pp->param->texture, + pp->param->texture_id); } } @@ -757,7 +762,7 @@ void gs_shader_set_val(gs_sparam_t *param, const void *val, size_t size) expected_size = sizeof(float) * 4 * 4; break; case GS_SHADER_PARAM_TEXTURE: - expected_size = sizeof(void *); + expected_size = sizeof(struct gs_shader_texture); break; default: expected_size = 0; @@ -773,10 +778,14 @@ void gs_shader_set_val(gs_sparam_t *param, const void *val, size_t size) return; } - if (param->type == GS_SHADER_PARAM_TEXTURE) - gs_shader_set_texture(param, *(gs_texture_t **)val); - else + if (param->type == GS_SHADER_PARAM_TEXTURE) { + struct gs_shader_texture shader_tex; + memcpy(&shader_tex, val, sizeof(shader_tex)); + gs_shader_set_texture(param, shader_tex.tex); + param->srgb = shader_tex.srgb; + } else { da_copy_array(param->cur_value, val, size); + } } void gs_shader_set_default(gs_sparam_t *param) diff --git a/libobs-opengl/gl-shaderparser.c b/libobs-opengl/gl-shaderparser.c index afb2dff05..bb653d0a8 100644 --- a/libobs-opengl/gl-shaderparser.c +++ b/libobs-opengl/gl-shaderparser.c @@ -416,20 +416,19 @@ static bool gl_write_texture_code(struct gl_shader_parser *glsp, const char *function_end = ")"; - if (cf_token_is(cfp, "Sample")) + if (cf_token_is(cfp, "Sample")) { written = gl_write_texture_call(glsp, var, "texture", true); - else if (cf_token_is(cfp, "SampleBias")) + } else if (cf_token_is(cfp, "SampleBias")) { written = gl_write_texture_call(glsp, var, "texture", true); - else if (cf_token_is(cfp, "SampleGrad")) + } else if (cf_token_is(cfp, "SampleGrad")) { written = gl_write_texture_call(glsp, var, "textureGrad", true); - else if (cf_token_is(cfp, "SampleLevel")) + } else if (cf_token_is(cfp, "SampleLevel")) { written = gl_write_texture_call(glsp, var, "textureLod", true); - else if (cf_token_is(cfp, "Load")) { - written = gl_write_texture_call(glsp, var, "texelFetch", false); - dstr_cat(&glsp->gl_string, "("); - function_end = (strcmp(var->type, "texture3d") == 0) - ? ").xyz, 0)" - : ").xy, 0)"; + } else if (cf_token_is(cfp, "Load")) { + const char *const func = (strcmp(var->type, "texture3d") == 0) + ? "obs_load_3d" + : "obs_load_2d"; + written = gl_write_texture_call(glsp, var, func, false); } if (!written) @@ -744,6 +743,26 @@ static bool gl_shader_buildstring(struct gl_shader_parser *glsp) dstr_copy(&glsp->gl_string, "#version 330\n\n"); dstr_cat(&glsp->gl_string, "const bool obs_glsl_compile = true;\n\n"); + dstr_cat(&glsp->gl_string, + "vec4 obs_load_2d(sampler2D s, ivec3 p_lod)\n"); + dstr_cat(&glsp->gl_string, "{\n"); + dstr_cat(&glsp->gl_string, "\tint lod = p_lod.z;\n"); + dstr_cat(&glsp->gl_string, "\tvec2 size = textureSize(s, lod);\n"); + dstr_cat(&glsp->gl_string, + "\tvec2 p = (vec2(p_lod.xy) + 0.5) / size;\n"); + dstr_cat(&glsp->gl_string, "\tvec4 color = textureLod(s, p, lod);\n"); + dstr_cat(&glsp->gl_string, "\treturn color;\n"); + dstr_cat(&glsp->gl_string, "}\n\n"); + dstr_cat(&glsp->gl_string, + "vec4 obs_load_3d(sampler3D s, ivec4 p_lod)\n"); + dstr_cat(&glsp->gl_string, "{\n"); + dstr_cat(&glsp->gl_string, "\tint lod = p_lod.w;\n"); + dstr_cat(&glsp->gl_string, "\tvec3 size = textureSize(s, lod);\n"); + dstr_cat(&glsp->gl_string, + "\tvec3 p = (vec3(p_lod.xyz) + 0.5) / size;\n"); + dstr_cat(&glsp->gl_string, "\tvec4 color = textureLod(s, p, lod);\n"); + dstr_cat(&glsp->gl_string, "\treturn color;\n"); + dstr_cat(&glsp->gl_string, "}\n\n"); gl_write_params(glsp); gl_write_inputs(glsp, main_func); gl_write_outputs(glsp, main_func); diff --git a/libobs-opengl/gl-subsystem.c b/libobs-opengl/gl-subsystem.c index deede9568..da7fed3da 100644 --- a/libobs-opengl/gl-subsystem.c +++ b/libobs-opengl/gl-subsystem.c @@ -138,6 +138,12 @@ static bool gl_init_extensions(struct gs_device *device) gl_enable_debug(); + if (!GLAD_GL_EXT_texture_sRGB_decode) { + blog(LOG_ERROR, "OpenGL extension EXT_texture_sRGB_decode " + "is required."); + return false; + } + gl_enable(GL_TEXTURE_CUBE_MAP_SEAMLESS); if (GLAD_GL_VERSION_4_3 || GLAD_GL_ARB_copy_image) @@ -245,6 +251,16 @@ int device_create(gs_device_t **p_device, uint32_t adapter) gl_enable(GL_CULL_FACE); gl_gen_vertex_arrays(1, &device->empty_vao); + struct gs_sampler_info raw_load_info; + raw_load_info.filter = GS_FILTER_POINT; + raw_load_info.address_u = GS_ADDRESS_BORDER; + raw_load_info.address_v = GS_ADDRESS_BORDER; + raw_load_info.address_w = GS_ADDRESS_BORDER; + raw_load_info.max_anisotropy = 1; + raw_load_info.border_color = 0; + device->raw_load_sampler = + device_samplerstate_create(device, &raw_load_info); + gl_clear_context(device); device->cur_swap = NULL; @@ -273,6 +289,7 @@ void device_destroy(gs_device_t *device) while (device->first_program) gs_program_destroy(device->first_program); + samplerstate_release(device->raw_load_sampler); gl_delete_vertex_arrays(1, &device->empty_vao); da_free(device->proj_stack); @@ -481,7 +498,8 @@ static inline struct gs_shader_param *get_texture_param(gs_device_t *device, return NULL; } -void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) +static void device_load_texture_internal(gs_device_t *device, gs_texture_t *tex, + int unit, GLint decode) { struct gs_shader_param *param; struct gs_sampler_state *sampler; @@ -512,14 +530,17 @@ void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) if (!tex) return; - // texelFetch doesn't need a sampler if (param->sampler_id != (size_t)-1) sampler = device->cur_samplers[param->sampler_id]; else - sampler = NULL; + sampler = device->raw_load_sampler; if (!gl_bind_texture(tex->gl_target, tex->texture)) goto fail; + + if (!gl_tex_param_i(tex->gl_target, GL_TEXTURE_SRGB_DECODE_EXT, decode)) + goto fail; + if (sampler && !load_texture_sampler(tex, sampler)) goto fail; @@ -529,6 +550,16 @@ fail: blog(LOG_ERROR, "device_load_texture (GL) failed"); } +void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) +{ + device_load_texture_internal(device, tex, unit, GL_SKIP_DECODE_EXT); +} + +void device_load_texture_srgb(gs_device_t *device, gs_texture_t *tex, int unit) +{ + device_load_texture_internal(device, tex, unit, GL_DECODE_EXT); +} + static bool load_sampler_on_textures(gs_device_t *device, gs_samplerstate_t *ss, int sampler_unit) { @@ -853,6 +884,21 @@ fail: blog(LOG_ERROR, "device_set_cube_render_target (GL) failed"); } +void device_enable_framebuffer_srgb(gs_device_t *device, bool enable) +{ + if (enable) + gl_enable(GL_FRAMEBUFFER_SRGB); + else + gl_disable(GL_FRAMEBUFFER_SRGB); +} + +bool device_framebuffer_srgb_enabled(gs_device_t *device) +{ + const GLboolean enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); + gl_success("glIsEnabled"); + return enabled == GL_TRUE; +} + void device_copy_texture_region(gs_device_t *device, gs_texture_t *dst, uint32_t dst_x, uint32_t dst_y, gs_texture_t *src, uint32_t src_x, diff --git a/libobs-opengl/gl-subsystem.h b/libobs-opengl/gl-subsystem.h index f471f21ed..c851243bd 100644 --- a/libobs-opengl/gl-subsystem.h +++ b/libobs-opengl/gl-subsystem.h @@ -71,6 +71,12 @@ static inline GLenum convert_gs_format(enum gs_color_format format) return GL_RGBA; case GS_DXT5: return GL_RGBA; + case GS_RGBA_UNORM: + return GL_RGBA; + case GS_BGRX_UNORM: + return GL_BGRA; + case GS_BGRA_UNORM: + return GL_BGRA; case GS_UNKNOWN: return 0; } @@ -86,11 +92,11 @@ static inline GLenum convert_gs_internal_format(enum gs_color_format format) case GS_R8: return GL_R8; case GS_RGBA: - return GL_RGBA; + return GL_SRGB8_ALPHA8; case GS_BGRX: - return GL_RGB; + return GL_SRGB8; case GS_BGRA: - return GL_RGBA; + return GL_SRGB8_ALPHA8; case GS_R10G10B10A2: return GL_RGB10_A2; case GS_RGBA16: @@ -117,6 +123,12 @@ static inline GLenum convert_gs_internal_format(enum gs_color_format format) return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; case GS_DXT5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case GS_RGBA_UNORM: + return GL_RGBA; + case GS_BGRX_UNORM: + return GL_RGB; + case GS_BGRA_UNORM: + return GL_RGBA; case GS_UNKNOWN: return 0; } @@ -163,6 +175,12 @@ static inline GLenum get_gl_format_type(enum gs_color_format format) return GL_UNSIGNED_BYTE; case GS_DXT5: return GL_UNSIGNED_BYTE; + case GS_RGBA_UNORM: + return GL_UNSIGNED_BYTE; + case GS_BGRX_UNORM: + return GL_UNSIGNED_BYTE; + case GS_BGRA_UNORM: + return GL_UNSIGNED_BYTE; case GS_UNKNOWN: return 0; } @@ -411,6 +429,7 @@ struct gs_shader_param { int array_count; struct gs_texture *texture; + bool srgb; DARRAY(uint8_t) cur_value; DARRAY(uint8_t) def_value; @@ -596,6 +615,7 @@ struct gs_device { enum copy_type copy_type; GLuint empty_vao; + gs_samplerstate_t *raw_load_sampler; gs_texture_t *cur_render_target; gs_zstencil_t *cur_zstencil_buffer; diff --git a/libobs-winrt/winrt-capture.cpp b/libobs-winrt/winrt-capture.cpp index 3cc14e7b1..9dfc33c00 100644 --- a/libobs-winrt/winrt-capture.cpp +++ b/libobs-winrt/winrt-capture.cpp @@ -186,8 +186,10 @@ struct winrt_capture { } if (!texture) { - texture = gs_texture_create_gdi(texture_width, - texture_height); + texture = gs_texture_create(texture_width, + texture_height, + GS_BGRA, 1, NULL, + 0); } if (client_area) { @@ -491,7 +493,15 @@ static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect) gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); size_t passes; - gs_effect_set_texture(image, texture); + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) + gs_effect_set_texture_srgb(image, texture); + else + gs_effect_set_texture(image, texture); passes = gs_technique_begin(tech); for (size_t i = 0; i < passes; i++) { @@ -502,6 +512,8 @@ static void draw_texture(struct winrt_capture *capture, gs_effect_t *effect) } } gs_technique_end(tech); + + gs_enable_framebuffer_srgb(previous); } extern "C" EXPORT BOOL winrt_capture_active(const struct winrt_capture *capture) diff --git a/libobs/graphics/device-exports.h b/libobs/graphics/device-exports.h index 9b4e14b79..0620c6372 100644 --- a/libobs/graphics/device-exports.h +++ b/libobs/graphics/device-exports.h @@ -88,6 +88,8 @@ EXPORT void device_load_indexbuffer(gs_device_t *device, gs_indexbuffer_t *indexbuffer); EXPORT void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit); +EXPORT void device_load_texture_srgb(gs_device_t *device, gs_texture_t *tex, + int unit); EXPORT void device_load_samplerstate(gs_device_t *device, gs_samplerstate_t *samplerstate, int unit); EXPORT void device_load_vertexshader(gs_device_t *device, @@ -105,6 +107,8 @@ EXPORT void device_set_render_target(gs_device_t *device, gs_texture_t *tex, EXPORT void device_set_cube_render_target(gs_device_t *device, gs_texture_t *cubetex, int side, gs_zstencil_t *zstencil); +EXPORT void device_enable_framebuffer_srgb(gs_device_t *device, bool enable); +EXPORT bool device_framebuffer_srgb_enabled(gs_device_t *device); EXPORT void device_copy_texture(gs_device_t *device, gs_texture_t *dst, gs_texture_t *src); EXPORT void device_copy_texture_region(gs_device_t *device, gs_texture_t *dst, diff --git a/libobs/graphics/effect.c b/libobs/graphics/effect.c index 4425dbf7a..975653b95 100644 --- a/libobs/graphics/effect.c +++ b/libobs/graphics/effect.c @@ -486,7 +486,18 @@ void gs_effect_set_color(gs_eparam_t *param, uint32_t argb) void gs_effect_set_texture(gs_eparam_t *param, gs_texture_t *val) { - effect_setval_inline(param, &val, sizeof(gs_texture_t *)); + struct gs_shader_texture shader_tex; + shader_tex.tex = val; + shader_tex.srgb = false; + effect_setval_inline(param, &shader_tex, sizeof(shader_tex)); +} + +void gs_effect_set_texture_srgb(gs_eparam_t *param, gs_texture_t *val) +{ + struct gs_shader_texture shader_tex; + shader_tex.tex = val; + shader_tex.srgb = true; + effect_setval_inline(param, &shader_tex, sizeof(shader_tex)); } void gs_effect_set_val(gs_eparam_t *param, const void *val, size_t size) diff --git a/libobs/graphics/graphics-imports.c b/libobs/graphics/graphics-imports.c index 2a68f749f..c12a993c3 100644 --- a/libobs/graphics/graphics-imports.c +++ b/libobs/graphics/graphics-imports.c @@ -82,6 +82,8 @@ bool load_graphics_imports(struct gs_exports *exports, void *module, GRAPHICS_IMPORT(device_get_zstencil_target); GRAPHICS_IMPORT(device_set_render_target); GRAPHICS_IMPORT(device_set_cube_render_target); + GRAPHICS_IMPORT(device_enable_framebuffer_srgb); + GRAPHICS_IMPORT(device_framebuffer_srgb_enabled); GRAPHICS_IMPORT(device_copy_texture_region); GRAPHICS_IMPORT(device_copy_texture); GRAPHICS_IMPORT(device_stage_texture); diff --git a/libobs/graphics/graphics-internal.h b/libobs/graphics/graphics-internal.h index 230ae6ff1..260837160 100644 --- a/libobs/graphics/graphics-internal.h +++ b/libobs/graphics/graphics-internal.h @@ -106,6 +106,9 @@ struct gs_exports { void (*device_set_cube_render_target)(gs_device_t *device, gs_texture_t *cubetex, int side, gs_zstencil_t *zstencil); + void (*device_enable_framebuffer_srgb)(gs_device_t *device, + bool enable); + bool (*device_framebuffer_srgb_enabled)(gs_device_t *device); void (*device_copy_texture)(gs_device_t *device, gs_texture_t *dst, gs_texture_t *src); void (*device_copy_texture_region)(gs_device_t *device, @@ -362,4 +365,6 @@ struct graphics_subsystem { struct blend_state cur_blend_state; DARRAY(struct blend_state) blend_state_stack; + + bool linear_srgb; }; diff --git a/libobs/graphics/graphics.c b/libobs/graphics/graphics.c index 7c0f99d25..d15fd9881 100644 --- a/libobs/graphics/graphics.c +++ b/libobs/graphics/graphics.c @@ -1708,6 +1708,50 @@ void gs_set_cube_render_target(gs_texture_t *cubetex, int side, graphics->device, cubetex, side, zstencil); } +void gs_enable_framebuffer_srgb(bool enable) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_enable_framebuffer_srgb")) + return; + + graphics->exports.device_enable_framebuffer_srgb(graphics->device, + enable); +} + +bool gs_framebuffer_srgb_enabled(void) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_framebuffer_srgb_enabled")) + return false; + + return graphics->exports.device_framebuffer_srgb_enabled( + graphics->device); +} + +bool gs_get_linear_srgb(void) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_get_linear_srgb")) + return false; + + return graphics->linear_srgb; +} + +bool gs_set_linear_srgb(bool linear_srgb) +{ + graphics_t *graphics = thread_graphics; + + if (!gs_valid("gs_set_linear_srgb")) + return false; + + const bool previous = graphics->linear_srgb; + graphics->linear_srgb = linear_srgb; + return previous; +} + void gs_copy_texture(gs_texture_t *dst, gs_texture_t *src) { graphics_t *graphics = thread_graphics; diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index d356175fa..ec1eb9979 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -73,6 +73,9 @@ enum gs_color_format { GS_DXT3, GS_DXT5, GS_R8G8, + GS_RGBA_UNORM, + GS_BGRX_UNORM, + GS_BGRA_UNORM, }; enum gs_zstencil_format { @@ -302,6 +305,11 @@ enum gs_shader_param_type { GS_SHADER_PARAM_TEXTURE, }; +struct gs_shader_texture { + gs_texture_t *tex; + bool srgb; +}; + #ifndef SWIG struct gs_shader_param_info { enum gs_shader_param_type type; @@ -423,6 +431,7 @@ EXPORT void gs_effect_set_vec2(gs_eparam_t *param, const struct vec2 *val); EXPORT void gs_effect_set_vec3(gs_eparam_t *param, const struct vec3 *val); EXPORT void gs_effect_set_vec4(gs_eparam_t *param, const struct vec4 *val); EXPORT void gs_effect_set_texture(gs_eparam_t *param, gs_texture_t *val); +EXPORT void gs_effect_set_texture_srgb(gs_eparam_t *param, gs_texture_t *val); EXPORT void gs_effect_set_val(gs_eparam_t *param, const void *val, size_t size); EXPORT void gs_effect_set_default(gs_eparam_t *param); EXPORT size_t gs_effect_get_val_size(gs_eparam_t *param); @@ -667,6 +676,12 @@ EXPORT void gs_set_render_target(gs_texture_t *tex, gs_zstencil_t *zstencil); EXPORT void gs_set_cube_render_target(gs_texture_t *cubetex, int side, gs_zstencil_t *zstencil); +EXPORT void gs_enable_framebuffer_srgb(bool enable); +EXPORT bool gs_framebuffer_srgb_enabled(void); + +EXPORT bool gs_get_linear_srgb(void); +EXPORT bool gs_set_linear_srgb(bool linear_srgb); + EXPORT void gs_copy_texture(gs_texture_t *dst, gs_texture_t *src); EXPORT void gs_copy_texture_region(gs_texture_t *dst, uint32_t dst_x, uint32_t dst_y, gs_texture_t *src, @@ -939,6 +954,12 @@ static inline uint32_t gs_get_format_bpp(enum gs_color_format format) return 8; case GS_R8G8: return 16; + case GS_RGBA_UNORM: + return 32; + case GS_BGRX_UNORM: + return 32; + case GS_BGRA_UNORM: + return 32; case GS_UNKNOWN: return 0; } @@ -951,6 +972,18 @@ static inline bool gs_is_compressed_format(enum gs_color_format format) return (format == GS_DXT1 || format == GS_DXT3 || format == GS_DXT5); } +static inline bool gs_is_srgb_format(enum gs_color_format format) +{ + switch (format) { + case GS_RGBA: + case GS_BGRX: + case GS_BGRA: + return true; + default: + return false; + } +} + static inline uint32_t gs_get_total_levels(uint32_t width, uint32_t height, uint32_t depth) { diff --git a/libobs/graphics/vec4.h b/libobs/graphics/vec4.h index 80403a739..f5c1c2411 100644 --- a/libobs/graphics/vec4.h +++ b/libobs/graphics/vec4.h @@ -199,43 +199,126 @@ static inline void vec4_ceil(struct vec4 *dst, const struct vec4 *v) static inline uint32_t vec4_to_rgba(const struct vec4 *src) { uint32_t val; - val = (uint32_t)((double)src->x * 255.0); - val |= (uint32_t)((double)src->y * 255.0) << 8; - val |= (uint32_t)((double)src->z * 255.0) << 16; - val |= (uint32_t)((double)src->w * 255.0) << 24; + val = (uint32_t)((src->x * 255.0f) + 0.5f); + val |= (uint32_t)((src->y * 255.0f) + 0.5f) << 8; + val |= (uint32_t)((src->z * 255.0f) + 0.5f) << 16; + val |= (uint32_t)((src->w * 255.0f) + 0.5f) << 24; return val; } static inline uint32_t vec4_to_bgra(const struct vec4 *src) { uint32_t val; - val = (uint32_t)((double)src->z * 255.0); - val |= (uint32_t)((double)src->y * 255.0) << 8; - val |= (uint32_t)((double)src->x * 255.0) << 16; - val |= (uint32_t)((double)src->w * 255.0) << 24; + val = (uint32_t)((src->z * 255.0f) + 0.5f); + val |= (uint32_t)((src->y * 255.0f) + 0.5f) << 8; + val |= (uint32_t)((src->x * 255.0f) + 0.5f) << 16; + val |= (uint32_t)((src->w * 255.0f) + 0.5f) << 24; return val; } static inline void vec4_from_rgba(struct vec4 *dst, uint32_t rgba) { - dst->x = (float)((double)(rgba & 0xFF) * (1.0 / 255.0)); + dst->x = (float)(rgba & 0xFF) / 255.0f; rgba >>= 8; - dst->y = (float)((double)(rgba & 0xFF) * (1.0 / 255.0)); + dst->y = (float)(rgba & 0xFF) / 255.0f; rgba >>= 8; - dst->z = (float)((double)(rgba & 0xFF) * (1.0 / 255.0)); + dst->z = (float)(rgba & 0xFF) / 255.0f; rgba >>= 8; - dst->w = (float)((double)(rgba & 0xFF) * (1.0 / 255.0)); + dst->w = (float)rgba / 255.0f; } static inline void vec4_from_bgra(struct vec4 *dst, uint32_t bgra) { - dst->z = (float)((double)(bgra & 0xFF) * (1.0 / 255.0)); + dst->z = (float)(bgra & 0xFF) / 255.0f; bgra >>= 8; - dst->y = (float)((double)(bgra & 0xFF) * (1.0 / 255.0)); + dst->y = (float)(bgra & 0xFF) / 255.0f; bgra >>= 8; - dst->x = (float)((double)(bgra & 0xFF) * (1.0 / 255.0)); + dst->x = (float)(bgra & 0xFF) / 255.0f; bgra >>= 8; - dst->w = (float)((double)(bgra & 0xFF) * (1.0 / 255.0)); + dst->w = (float)bgra / 255.0f; +} + +static inline float srgb_nonlinear_to_linear(float u) +{ + return (u <= 0.04045f) ? (u / 12.92f) + : powf((u + 0.055f) / 1.055f, 2.4f); +} + +static inline void vec4_from_rgba_srgb(struct vec4 *dst, uint32_t rgba) +{ + dst->x = srgb_nonlinear_to_linear((float)(rgba & 0xFF) / 255.0f); + rgba >>= 8; + dst->y = srgb_nonlinear_to_linear((float)(rgba & 0xFF) / 255.0f); + rgba >>= 8; + dst->z = srgb_nonlinear_to_linear((float)(rgba & 0xFF) / 255.0f); + rgba >>= 8; + dst->w = (float)rgba / 255.0f; +} + +static inline void vec4_from_bgra_srgb(struct vec4 *dst, uint32_t bgra) +{ + dst->z = srgb_nonlinear_to_linear((float)(bgra & 0xFF) / 255.0f); + bgra >>= 8; + dst->y = srgb_nonlinear_to_linear((float)(bgra & 0xFF) / 255.0f); + bgra >>= 8; + dst->x = srgb_nonlinear_to_linear((float)(bgra & 0xFF) / 255.0f); + bgra >>= 8; + dst->w = (float)bgra / 255.0f; +} + +static inline void vec4_from_rgba_srgb_premultiply(struct vec4 *dst, + uint32_t rgba) +{ + vec4_from_rgba_srgb(dst, rgba); + dst->x *= dst->w; + dst->y *= dst->w; + dst->z *= dst->w; +} + +static inline void vec4_from_bgra_srgb_premultiply(struct vec4 *dst, + uint32_t bgra) +{ + vec4_from_bgra_srgb(dst, bgra); + dst->x *= dst->w; + dst->y *= dst->w; + dst->z *= dst->w; +} + +static inline float srgb_linear_to_nonlinear(float u) +{ + return (u <= 0.0031308f) ? (12.92f * u) + : ((1.055f * powf(u, 1.0f / 2.4f)) - 0.055f); +} + +static inline uint32_t vec4_to_rgba_srgb(const struct vec4 *src) +{ + uint32_t val; + val = (uint32_t)((srgb_linear_to_nonlinear(src->x) * 255.0f) + 0.5f); + val |= (uint32_t)((srgb_linear_to_nonlinear(src->y) * 255.0f) + 0.5f) + << 8; + val |= (uint32_t)((srgb_linear_to_nonlinear(src->z) * 255.0f) + 0.5f) + << 16; + val |= (uint32_t)((src->w * 255.0f) + 0.5f) << 24; + return val; +} + +static inline uint32_t vec4_to_bgra_srgb(const struct vec4 *src) +{ + uint32_t val; + val = (uint32_t)((srgb_linear_to_nonlinear(src->z) * 255.0f) + 0.5f); + val |= (uint32_t)((srgb_linear_to_nonlinear(src->y) * 255.0f) + 0.5f) + << 8; + val |= (uint32_t)((srgb_linear_to_nonlinear(src->x) * 255.0f) + 0.5f) + << 16; + val |= (uint32_t)((src->w * 255.0f) + 0.5f) << 24; + return val; +} + +static inline void vec4_srgb_linear_to_nonlinear(struct vec4 *dst) +{ + dst->x = srgb_linear_to_nonlinear(dst->x); + dst->y = srgb_linear_to_nonlinear(dst->y); + dst->y = srgb_linear_to_nonlinear(dst->y); } EXPORT void vec4_transform(struct vec4 *dst, const struct vec4 *v, diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 5a8a8e81c..df46085f3 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -570,6 +570,7 @@ static inline void render_item(struct obs_scene_item *item) } } + const bool previous = gs_set_linear_srgb(true); gs_matrix_push(); gs_matrix_mul(&item->draw_transform); if (item->item_render) { @@ -578,6 +579,7 @@ static inline void render_item(struct obs_scene_item *item) obs_source_video_render(item->source); } gs_matrix_pop(); + gs_set_linear_srgb(previous); cleanup: GS_DEBUG_MARKER_END(); diff --git a/libobs/obs-source-deinterlace.c b/libobs/obs-source-deinterlace.c index bce0e2ab2..81fbcdf09 100644 --- a/libobs/obs-source-deinterlace.c +++ b/libobs/obs-source-deinterlace.c @@ -346,6 +346,25 @@ static inline gs_effect_t *get_effect(enum obs_deinterlace_mode mode) return NULL; } +static bool deinterlace_linear_required(enum obs_deinterlace_mode mode) +{ + switch (mode) { + case OBS_DEINTERLACE_MODE_DISABLE: + case OBS_DEINTERLACE_MODE_DISCARD: + case OBS_DEINTERLACE_MODE_RETRO: + return false; + case OBS_DEINTERLACE_MODE_BLEND: + case OBS_DEINTERLACE_MODE_BLEND_2X: + case OBS_DEINTERLACE_MODE_LINEAR: + case OBS_DEINTERLACE_MODE_LINEAR_2X: + case OBS_DEINTERLACE_MODE_YADIF: + case OBS_DEINTERLACE_MODE_YADIF_2X: + return true; + } + + return false; +} + void deinterlace_render(obs_source_t *s) { gs_effect_t *effect = s->deinterlace_effect; @@ -372,8 +391,21 @@ void deinterlace_render(obs_source_t *s) if (!cur_tex || !prev_tex || !s->async_width || !s->async_height) return; - gs_effect_set_texture(image, cur_tex); - gs_effect_set_texture(prev, prev_tex); + const bool linear_srgb = + gs_get_linear_srgb() || + deinterlace_linear_required(s->deinterlace_mode); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) { + gs_effect_set_texture_srgb(image, cur_tex); + gs_effect_set_texture_srgb(prev, prev_tex); + } else { + gs_effect_set_texture(image, cur_tex); + gs_effect_set_texture(prev, prev_tex); + } + gs_effect_set_int(field, s->deinterlace_top_first); gs_effect_set_vec2(dimensions, &size); @@ -385,6 +417,8 @@ void deinterlace_render(obs_source_t *s) while (gs_effect_loop(effect, "Draw")) gs_draw_sprite(NULL, s->async_flip ? GS_FLIP_V : 0, s->async_width, s->async_height); + + gs_enable_framebuffer_srgb(previous); } static void enable_deinterlacing(obs_source_t *source, diff --git a/libobs/obs-source.c b/libobs/obs-source.c index b87a7eacb..657e1d39b 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -2012,9 +2012,21 @@ static inline void obs_source_draw_texture(struct obs_source *source, tex = gs_texrender_get_texture(source->async_texrender); param = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture(param, tex); + + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) { + gs_effect_set_texture_srgb(param, tex); + } else { + gs_effect_set_texture(param, tex); + } gs_draw_sprite(tex, source->async_flip ? GS_FLIP_V : 0, 0, 0); + + gs_enable_framebuffer_srgb(previous); } static void obs_source_draw_async_texture(struct obs_source *source) @@ -3588,7 +3600,15 @@ static inline void render_filter_tex(gs_texture_t *tex, gs_effect_t *effect, gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); size_t passes, i; - gs_effect_set_texture(image, tex); + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) + gs_effect_set_texture_srgb(image, tex); + else + gs_effect_set_texture(image, tex); passes = gs_technique_begin(tech); for (i = 0; i < passes; i++) { @@ -3597,6 +3617,8 @@ static inline void render_filter_tex(gs_texture_t *tex, gs_effect_t *effect, gs_technique_end_pass(tech); } gs_technique_end(tech); + + gs_enable_framebuffer_srgb(previous); } static inline bool can_bypass(obs_source_t *target, obs_source_t *parent, @@ -4164,21 +4186,27 @@ void obs_source_draw_set_color_matrix(const struct matrix4 *color_matrix, void obs_source_draw(gs_texture_t *texture, int x, int y, uint32_t cx, uint32_t cy, bool flip) { - gs_effect_t *effect = gs_get_effect(); - bool change_pos = (x != 0 || y != 0); - gs_eparam_t *image; + if (!obs_ptr_valid(texture, "obs_source_draw")) + return; + gs_effect_t *effect = gs_get_effect(); if (!effect) { blog(LOG_WARNING, "obs_source_draw: no active effect!"); return; } - if (!obs_ptr_valid(texture, "obs_source_draw")) - return; + const bool linear_srgb = gs_get_linear_srgb(); - image = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture(image, texture); + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); + if (linear_srgb) + gs_effect_set_texture_srgb(image, texture); + else + gs_effect_set_texture(image, texture); + + const bool change_pos = (x != 0 || y != 0); if (change_pos) { gs_matrix_push(); gs_matrix_translate3f((float)x, (float)y, 0.0f); @@ -4188,6 +4216,8 @@ void obs_source_draw(gs_texture_t *texture, int x, int y, uint32_t cx, if (change_pos) gs_matrix_pop(); + + gs_enable_framebuffer_srgb(previous); } void obs_source_inc_showing(obs_source_t *source) diff --git a/libobs/obs-video.c b/libobs/obs-video.c index f1c019320..b7702d960 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -256,8 +256,9 @@ static inline gs_texture_t *render_output_texture(struct obs_core_video *video) gs_effect_set_vec2(bres_i, &base_i); } - gs_effect_set_texture(image, texture); + gs_effect_set_texture_srgb(image, texture); + gs_enable_framebuffer_srgb(true); gs_enable_blending(false); passes = gs_technique_begin(tech); for (i = 0; i < passes; i++) { @@ -267,6 +268,7 @@ static inline gs_texture_t *render_output_texture(struct obs_core_video *video) } gs_technique_end(tech); gs_enable_blending(true); + gs_enable_framebuffer_srgb(false); profile_end(render_output_texture_name); diff --git a/plugins/image-source/color-source.c b/plugins/image-source/color-source.c index b2e087470..26bb477ef 100644 --- a/plugins/image-source/color-source.c +++ b/plugins/image-source/color-source.c @@ -1,7 +1,8 @@ #include struct color_source { - uint32_t color; + struct vec4 color; + struct vec4 color_srgb; uint32_t width; uint32_t height; @@ -22,7 +23,8 @@ static void color_source_update(void *data, obs_data_t *settings) uint32_t width = (uint32_t)obs_data_get_int(settings, "width"); uint32_t height = (uint32_t)obs_data_get_int(settings, "height"); - context->color = color; + vec4_from_rgba(&context->color, color); + vec4_from_rgba_srgb(&context->color_srgb, color); context->width = width; context->height = height; } @@ -50,8 +52,8 @@ static obs_properties_t *color_source_properties(void *unused) obs_properties_t *props = obs_properties_create(); - obs_properties_add_color(props, "color", - obs_module_text("ColorSource.Color")); + obs_properties_add_color_alpha(props, "color", + obs_module_text("ColorSource.Color")); obs_properties_add_int(props, "width", obs_module_text("ColorSource.Width"), 0, 4096, @@ -64,19 +66,14 @@ static obs_properties_t *color_source_properties(void *unused) return props; } -static void color_source_render(void *data, gs_effect_t *effect) +static void color_source_render_helper(struct color_source *context, + struct vec4 *colorVal) { - UNUSED_PARAMETER(effect); - - struct color_source *context = data; - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - struct vec4 colorVal; - vec4_from_rgba(&colorVal, context->color); - gs_effect_set_vec4(color, &colorVal); + gs_effect_set_vec4(color, colorVal); gs_technique_begin(tech); gs_technique_begin_pass(tech, 0); @@ -87,6 +84,27 @@ static void color_source_render(void *data, gs_effect_t *effect) gs_technique_end(tech); } +static void color_source_render(void *data, gs_effect_t *effect) +{ + UNUSED_PARAMETER(effect); + + struct color_source *context = data; + + /* need linear path for correct alpha blending */ + const bool linear_srgb = gs_get_linear_srgb() || + (context->color.w < 1.0f); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) + color_source_render_helper(context, &context->color_srgb); + else + color_source_render_helper(context, &context->color); + + gs_enable_framebuffer_srgb(previous); +} + static uint32_t color_source_getwidth(void *data) { struct color_source *context = data; diff --git a/plugins/image-source/image-source.c b/plugins/image-source/image-source.c index 425554ed5..03787d271 100644 --- a/plugins/image-source/image-source.c +++ b/plugins/image-source/image-source.c @@ -147,10 +147,21 @@ static void image_source_render(void *data, gs_effect_t *effect) if (!context->if2.image.texture) return; - gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), - context->if2.image.texture); + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + gs_eparam_t *const param = gs_effect_get_param_by_name(effect, "image"); + if (linear_srgb) + gs_effect_set_texture_srgb(param, context->if2.image.texture); + else + gs_effect_set_texture(param, context->if2.image.texture); + gs_draw_sprite(context->if2.image.texture, 0, context->if2.image.cx, context->if2.image.cy); + + gs_enable_framebuffer_srgb(previous); } static void image_source_tick(void *data, float seconds) diff --git a/plugins/linux-capture/xcompcap-main.cpp b/plugins/linux-capture/xcompcap-main.cpp index 9d3b15333..1d7cd6df3 100644 --- a/plugins/linux-capture/xcompcap-main.cpp +++ b/plugins/linux-capture/xcompcap-main.cpp @@ -313,11 +313,11 @@ static gs_color_format gs_format_from_tex() // GS_RGBX format switch (iformat) { case GL_RGB: - return GS_BGRX; + return GS_BGRX_UNORM; case GL_RGBA: - return GS_RGBA; + return GS_RGBA_UNORM; default: - return GS_RGBA; + return GS_RGBA_UNORM; } } @@ -513,7 +513,7 @@ void XCompcapMain::updateSettings(obs_data_t *settings) XFree(configs); // Build an OBS texture to bind the pixmap to. - p->gltex = gs_texture_create(p->width, p->height, GS_RGBA, 1, 0, + p->gltex = gs_texture_create(p->width, p->height, GS_RGBA_UNORM, 1, 0, GS_GL_DUMMYTEX); GLuint gltex = *(GLuint *)gs_texture_get_obj(p->gltex); glBindTexture(GL_TEXTURE_2D, gltex); diff --git a/plugins/linux-capture/xcursor-xcb.c b/plugins/linux-capture/xcursor-xcb.c index a0306a63e..50a8bbd9a 100644 --- a/plugins/linux-capture/xcursor-xcb.c +++ b/plugins/linux-capture/xcursor-xcb.c @@ -93,9 +93,17 @@ void xcb_xcursor_render(xcb_xcursor_t *data) if (!data->tex) return; + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_effect_t *effect = gs_get_effect(); gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture(image, data->tex); + if (linear_srgb) + gs_effect_set_texture_srgb(image, data->tex); + else + gs_effect_set_texture(image, data->tex); gs_blend_state_push(); gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA); @@ -108,6 +116,8 @@ void xcb_xcursor_render(xcb_xcursor_t *data) gs_enable_color(true, true, true, true); gs_blend_state_pop(); + + gs_enable_framebuffer_srgb(previous); } void xcb_xcursor_offset(xcb_xcursor_t *data, const int x_org, const int y_org) diff --git a/plugins/linux-capture/xcursor.c b/plugins/linux-capture/xcursor.c index 021fd4244..820046d16 100644 --- a/plugins/linux-capture/xcursor.c +++ b/plugins/linux-capture/xcursor.c @@ -108,9 +108,17 @@ void xcursor_render(xcursor_t *data, int x_offset, int y_offset) if (!data->tex) return; + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_effect_t *effect = gs_get_effect(); gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture(image, data->tex); + if (linear_srgb) + gs_effect_set_texture_srgb(image, data->tex); + else + gs_effect_set_texture(image, data->tex); gs_blend_state_push(); gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA); @@ -124,6 +132,8 @@ void xcursor_render(xcursor_t *data, int x_offset, int y_offset) gs_enable_color(true, true, true, true); gs_blend_state_pop(); + + gs_enable_framebuffer_srgb(previous); } void xcursor_offset(xcursor_t *data, int_fast32_t x_org, int_fast32_t y_org) diff --git a/plugins/linux-capture/xshm-input.c b/plugins/linux-capture/xshm-input.c index 8d3b909fc..12433b690 100644 --- a/plugins/linux-capture/xshm-input.c +++ b/plugins/linux-capture/xshm-input.c @@ -523,13 +523,23 @@ static void xshm_video_render(void *vptr, gs_effect_t *effect) if (!data->texture) return; + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture(image, data->texture); + if (linear_srgb) + gs_effect_set_texture_srgb(image, data->texture); + else + gs_effect_set_texture(image, data->texture); while (gs_effect_loop(effect, "Draw")) { gs_draw_sprite(data->texture, 0, 0, 0); } + gs_enable_framebuffer_srgb(previous); + if (data->show_cursor) { effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); diff --git a/plugins/mac-capture/mac-display-capture.m b/plugins/mac-capture/mac-display-capture.m index 5fed6c440..37a9aafba 100644 --- a/plugins/mac-capture/mac-display-capture.m +++ b/plugins/mac-capture/mac-display-capture.m @@ -399,13 +399,21 @@ static void display_capture_video_render(void *data, gs_effect_t *effect) if (!dc->tex || (requires_window(dc->crop) && !dc->on_screen)) return; + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_vertexbuffer_flush(dc->vertbuf); gs_load_vertexbuffer(dc->vertbuf); gs_load_indexbuffer(NULL); gs_load_samplerstate(dc->sampler, 0); gs_technique_t *tech = gs_effect_get_technique(dc->effect, "Draw"); - gs_effect_set_texture(gs_effect_get_param_by_name(dc->effect, "image"), - dc->tex); + gs_eparam_t *param = gs_effect_get_param_by_name(dc->effect, "image"); + if (linear_srgb) + gs_effect_set_texture_srgb(param, dc->tex); + else + gs_effect_set_texture(param, dc->tex); gs_technique_begin(tech); gs_technique_begin_pass(tech, 0); @@ -413,6 +421,8 @@ static void display_capture_video_render(void *data, gs_effect_t *effect) gs_technique_end_pass(tech); gs_technique_end(tech); + + gs_enable_framebuffer_srgb(previous); } static const char *display_capture_getname(void *unused) diff --git a/plugins/obs-filters/chroma-key-filter.c b/plugins/obs-filters/chroma-key-filter.c index 071256e9e..fd4c4352a 100644 --- a/plugins/obs-filters/chroma-key-filter.c +++ b/plugins/obs-filters/chroma-key-filter.c @@ -54,19 +54,45 @@ struct chroma_key_filter_data { float spill; }; +struct chroma_key_filter_data_v2 { + obs_source_t *context; + + gs_effect_t *effect; + + gs_eparam_t *opacity_param; + gs_eparam_t *contrast_param; + gs_eparam_t *brightness_param; + gs_eparam_t *gamma_param; + + gs_eparam_t *pixel_size_param; + gs_eparam_t *chroma_param; + gs_eparam_t *similarity_param; + gs_eparam_t *smoothness_param; + gs_eparam_t *spill_param; + + float opacity; + float contrast; + float brightness; + float gamma; + + struct vec2 chroma; + float similarity; + float smoothness; + float spill; +}; + static const char *chroma_key_name(void *unused) { UNUSED_PARAMETER(unused); return obs_module_text("ChromaKeyFilter"); } -static const float yuv_mat[16] = {0.182586f, -0.100644f, 0.439216f, 0.0f, - 0.614231f, -0.338572f, -0.398942f, 0.0f, - 0.062007f, 0.439216f, -0.040274f, 0.0f, - 0.062745f, 0.501961f, 0.501961f, 1.0f}; +static const float cb_vec[] = {-0.100644f, -0.338572f, 0.439216f, 0.501961f}; +static const float cr_vec[] = {0.439216f, -0.398942f, -0.040274f, 0.501961f}; -static inline void color_settings_update(struct chroma_key_filter_data *filter, - obs_data_t *settings) +static inline void +color_settings_update_v1(struct chroma_key_filter_data *filter, + obs_data_t *settings) { uint32_t opacity = (uint32_t)obs_data_get_int(settings, SETTING_OPACITY); @@ -89,8 +115,29 @@ static inline void color_settings_update(struct chroma_key_filter_data *filter, vec4_from_rgba(&filter->color, color); } -static inline void chroma_settings_update(struct chroma_key_filter_data *filter, - obs_data_t *settings) +static inline void +color_settings_update_v2(struct chroma_key_filter_data_v2 *filter, + obs_data_t *settings) +{ + filter->opacity = + (float)obs_data_get_int(settings, SETTING_OPACITY) * 0.01f; + + double contrast = obs_data_get_double(settings, SETTING_CONTRAST); + contrast = (contrast < 0.0) ? (1.0 / (-contrast + 1.0)) + : (contrast + 1.0); + filter->contrast = (float)contrast; + + filter->brightness = + (float)obs_data_get_double(settings, SETTING_BRIGHTNESS); + + double gamma = obs_data_get_double(settings, SETTING_GAMMA); + gamma = (gamma < 0.0) ? (-gamma + 1.0) : (1.0 / (gamma + 1.0)); + filter->gamma = (float)gamma; +} + +static inline void +chroma_settings_update_v1(struct chroma_key_filter_data *filter, + obs_data_t *settings) { int64_t similarity = obs_data_get_int(settings, SETTING_SIMILARITY); int64_t smoothness = obs_data_get_int(settings, SETTING_SMOOTHNESS); @@ -100,8 +147,8 @@ static inline void chroma_settings_update(struct chroma_key_filter_data *filter, const char *key_type = obs_data_get_string(settings, SETTING_COLOR_TYPE); struct vec4 key_rgb; - struct vec4 key_color_v4; - struct matrix4 yuv_mat_m4; + struct vec4 cb_v4; + struct vec4 cr_v4; if (strcmp(key_type, "green") == 0) key_color = 0x00FF00; @@ -112,24 +159,67 @@ static inline void chroma_settings_update(struct chroma_key_filter_data *filter, vec4_from_rgba(&key_rgb, key_color | 0xFF000000); - memcpy(&yuv_mat_m4, yuv_mat, sizeof(yuv_mat)); - vec4_transform(&key_color_v4, &key_rgb, &yuv_mat_m4); - vec2_set(&filter->chroma, key_color_v4.y, key_color_v4.z); + memcpy(&cb_v4, cb_vec, sizeof(cb_v4)); + memcpy(&cr_v4, cr_vec, sizeof(cr_v4)); + filter->chroma.x = vec4_dot(&key_rgb, &cb_v4); + filter->chroma.y = vec4_dot(&key_rgb, &cr_v4); filter->similarity = (float)similarity / 1000.0f; filter->smoothness = (float)smoothness / 1000.0f; filter->spill = (float)spill / 1000.0f; } -static void chroma_key_update(void *data, obs_data_t *settings) +static inline void +chroma_settings_update_v2(struct chroma_key_filter_data_v2 *filter, + obs_data_t *settings) +{ + int64_t similarity = obs_data_get_int(settings, SETTING_SIMILARITY); + int64_t smoothness = obs_data_get_int(settings, SETTING_SMOOTHNESS); + int64_t spill = obs_data_get_int(settings, SETTING_SPILL); + uint32_t key_color = + (uint32_t)obs_data_get_int(settings, SETTING_KEY_COLOR); + const char *key_type = + obs_data_get_string(settings, SETTING_COLOR_TYPE); + struct vec4 key_rgb; + struct vec4 cb_v4; + struct vec4 cr_v4; + + if (strcmp(key_type, "green") == 0) + key_color = 0x00FF00; + else if (strcmp(key_type, "blue") == 0) + key_color = 0xFF9900; + else if (strcmp(key_type, "magenta") == 0) + key_color = 0xFF00FF; + + vec4_from_rgba_srgb(&key_rgb, key_color | 0xFF000000); + + memcpy(&cb_v4, cb_vec, sizeof(cb_v4)); + memcpy(&cr_v4, cr_vec, sizeof(cr_v4)); + filter->chroma.x = vec4_dot(&key_rgb, &cb_v4); + filter->chroma.y = vec4_dot(&key_rgb, &cr_v4); + + filter->similarity = (float)similarity / 1000.0f; + filter->smoothness = (float)smoothness / 1000.0f; + filter->spill = (float)spill / 1000.0f; +} + +static void chroma_key_update_v1(void *data, obs_data_t *settings) { struct chroma_key_filter_data *filter = data; - color_settings_update(filter, settings); - chroma_settings_update(filter, settings); + color_settings_update_v1(filter, settings); + chroma_settings_update_v1(filter, settings); } -static void chroma_key_destroy(void *data) +static void chroma_key_update_v2(void *data, obs_data_t *settings) +{ + struct chroma_key_filter_data_v2 *filter = data; + + color_settings_update_v2(filter, settings); + chroma_settings_update_v2(filter, settings); +} + +static void chroma_key_destroy_v1(void *data) { struct chroma_key_filter_data *filter = data; @@ -142,7 +232,20 @@ static void chroma_key_destroy(void *data) bfree(data); } -static void *chroma_key_create(obs_data_t *settings, obs_source_t *context) +static void chroma_key_destroy_v2(void *data) +{ + struct chroma_key_filter_data_v2 *filter = data; + + if (filter->effect) { + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + obs_leave_graphics(); + } + + bfree(data); +} + +static void *chroma_key_create_v1(obs_data_t *settings, obs_source_t *context) { struct chroma_key_filter_data *filter = bzalloc(sizeof(struct chroma_key_filter_data)); @@ -179,15 +282,60 @@ static void *chroma_key_create(obs_data_t *settings, obs_source_t *context) bfree(effect_path); if (!filter->effect) { - chroma_key_destroy(filter); + chroma_key_destroy_v1(filter); return NULL; } - chroma_key_update(filter, settings); + chroma_key_update_v1(filter, settings); return filter; } -static void chroma_key_render(void *data, gs_effect_t *effect) +static void *chroma_key_create_v2(obs_data_t *settings, obs_source_t *context) +{ + struct chroma_key_filter_data_v2 *filter = + bzalloc(sizeof(struct chroma_key_filter_data_v2)); + char *effect_path = obs_module_file("chroma_key_filter_v2.effect"); + + filter->context = context; + + obs_enter_graphics(); + + filter->effect = gs_effect_create_from_file(effect_path, NULL); + if (filter->effect) { + filter->opacity_param = + gs_effect_get_param_by_name(filter->effect, "opacity"); + filter->contrast_param = + gs_effect_get_param_by_name(filter->effect, "contrast"); + filter->brightness_param = gs_effect_get_param_by_name( + filter->effect, "brightness"); + filter->gamma_param = + gs_effect_get_param_by_name(filter->effect, "gamma"); + filter->chroma_param = gs_effect_get_param_by_name( + filter->effect, "chroma_key"); + filter->pixel_size_param = gs_effect_get_param_by_name( + filter->effect, "pixel_size"); + filter->similarity_param = gs_effect_get_param_by_name( + filter->effect, "similarity"); + filter->smoothness_param = gs_effect_get_param_by_name( + filter->effect, "smoothness"); + filter->spill_param = + gs_effect_get_param_by_name(filter->effect, "spill"); + } + + obs_leave_graphics(); + + bfree(effect_path); + + if (!filter->effect) { + chroma_key_destroy_v2(filter); + return NULL; + } + + chroma_key_update_v2(filter, settings); + return filter; +} + +static void chroma_key_render_v1(void *data, gs_effect_t *effect) { struct chroma_key_filter_data *filter = data; obs_source_t *target = obs_filter_get_target(filter->context); @@ -216,6 +364,37 @@ static void chroma_key_render(void *data, gs_effect_t *effect) UNUSED_PARAMETER(effect); } +static void chroma_key_render_v2(void *data, gs_effect_t *effect) +{ + struct chroma_key_filter_data_v2 *filter = data; + obs_source_t *target = obs_filter_get_target(filter->context); + uint32_t width = obs_source_get_base_width(target); + uint32_t height = obs_source_get_base_height(target); + struct vec2 pixel_size; + + if (!obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_ALLOW_DIRECT_RENDERING)) + return; + + vec2_set(&pixel_size, 1.0f / (float)width, 1.0f / (float)height); + + gs_effect_set_float(filter->opacity_param, filter->opacity); + gs_effect_set_float(filter->contrast_param, filter->contrast); + gs_effect_set_float(filter->brightness_param, filter->brightness); + gs_effect_set_float(filter->gamma_param, filter->gamma); + gs_effect_set_vec2(filter->chroma_param, &filter->chroma); + gs_effect_set_vec2(filter->pixel_size_param, &pixel_size); + gs_effect_set_float(filter->similarity_param, filter->similarity); + gs_effect_set_float(filter->smoothness_param, filter->smoothness); + gs_effect_set_float(filter->spill_param, filter->spill); + + const bool previous = gs_set_linear_srgb(true); + obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + gs_set_linear_srgb(previous); + + UNUSED_PARAMETER(effect); +} + static bool key_type_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings) { @@ -229,7 +408,7 @@ static bool key_type_changed(obs_properties_t *props, obs_property_t *p, return true; } -static obs_properties_t *chroma_key_properties(void *data) +static obs_properties_t *chroma_key_properties_v1(void *data) { obs_properties_t *props = obs_properties_create(); @@ -265,6 +444,42 @@ static obs_properties_t *chroma_key_properties(void *data) return props; } +static obs_properties_t *chroma_key_properties_v2(void *data) +{ + obs_properties_t *props = obs_properties_create(); + + obs_property_t *p = obs_properties_add_list(props, SETTING_COLOR_TYPE, + TEXT_COLOR_TYPE, + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, obs_module_text("Green"), "green"); + obs_property_list_add_string(p, obs_module_text("Blue"), "blue"); + obs_property_list_add_string(p, obs_module_text("Magenta"), "magenta"); + obs_property_list_add_string(p, obs_module_text("Custom"), "custom"); + + obs_property_set_modified_callback(p, key_type_changed); + + obs_properties_add_color(props, SETTING_KEY_COLOR, TEXT_KEY_COLOR); + obs_properties_add_int_slider(props, SETTING_SIMILARITY, + TEXT_SIMILARITY, 1, 1000, 1); + obs_properties_add_int_slider(props, SETTING_SMOOTHNESS, + TEXT_SMOOTHNESS, 1, 1000, 1); + obs_properties_add_int_slider(props, SETTING_SPILL, TEXT_SPILL, 1, 1000, + 1); + + obs_properties_add_int_slider(props, SETTING_OPACITY, TEXT_OPACITY, 0, + 100, 1); + obs_properties_add_float_slider(props, SETTING_CONTRAST, TEXT_CONTRAST, + -4.0, 4.0, 0.01); + obs_properties_add_float_slider(props, SETTING_BRIGHTNESS, + TEXT_BRIGHTNESS, -1.0, 1.0, 0.01); + obs_properties_add_float_slider(props, SETTING_GAMMA, TEXT_GAMMA, -1.0, + 1.0, 0.01); + + UNUSED_PARAMETER(data); + return props; +} + static void chroma_key_defaults(obs_data_t *settings) { obs_data_set_default_int(settings, SETTING_OPACITY, 100); @@ -281,12 +496,26 @@ static void chroma_key_defaults(obs_data_t *settings) struct obs_source_info chroma_key_filter = { .id = "chroma_key_filter", .type = OBS_SOURCE_TYPE_FILTER, - .output_flags = OBS_SOURCE_VIDEO, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, .get_name = chroma_key_name, - .create = chroma_key_create, - .destroy = chroma_key_destroy, - .video_render = chroma_key_render, - .update = chroma_key_update, - .get_properties = chroma_key_properties, + .create = chroma_key_create_v1, + .destroy = chroma_key_destroy_v1, + .video_render = chroma_key_render_v1, + .update = chroma_key_update_v1, + .get_properties = chroma_key_properties_v1, + .get_defaults = chroma_key_defaults, +}; + +struct obs_source_info chroma_key_filter_v2 = { + .id = "chroma_key_filter", + .version = 2, + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = chroma_key_name, + .create = chroma_key_create_v2, + .destroy = chroma_key_destroy_v2, + .video_render = chroma_key_render_v2, + .update = chroma_key_update_v2, + .get_properties = chroma_key_properties_v2, .get_defaults = chroma_key_defaults, }; diff --git a/plugins/obs-filters/color-correction-filter.c b/plugins/obs-filters/color-correction-filter.c index 7d2771cbb..54feae7f4 100644 --- a/plugins/obs-filters/color-correction-filter.c +++ b/plugins/obs-filters/color-correction-filter.c @@ -46,13 +46,7 @@ struct color_correction_filter_data { gs_eparam_t *gamma_param; gs_eparam_t *final_matrix_param; - struct vec3 gamma; - float contrast; - float brightness; - float saturation; - float hue_shift; - float opacity; - struct vec4 color; + float gamma; /* Pre-Computes */ struct matrix4 con_matrix; @@ -62,14 +56,26 @@ struct color_correction_filter_data { struct matrix4 color_matrix; struct matrix4 final_matrix; - struct vec3 rot_quaternion; - float rot_quaternion_w; - struct vec3 cross; - struct vec3 square; - struct vec3 wimag; - struct vec3 diag; - struct vec3 a_line; - struct vec3 b_line; + struct vec3 half_unit; +}; + +struct color_correction_filter_data_v2 { + obs_source_t *context; + + gs_effect_t *effect; + + gs_eparam_t *gamma_param; + gs_eparam_t *final_matrix_param; + + float gamma; + + /* Pre-Computes */ + struct matrix4 con_matrix; + struct matrix4 bright_matrix; + struct matrix4 sat_matrix; + struct matrix4 hue_op_matrix; + struct matrix4 final_matrix; + struct vec3 half_unit; }; @@ -94,40 +100,29 @@ static const char *color_correction_filter_name(void *unused) * with a slider this function is called to update the internal settings * in OBS, and hence the settings being passed to the CPU/GPU. */ -static void color_correction_filter_update(void *data, obs_data_t *settings) +static void color_correction_filter_update_v1(void *data, obs_data_t *settings) { struct color_correction_filter_data *filter = data; /* Build our Gamma numbers. */ double gamma = obs_data_get_double(settings, SETTING_GAMMA); gamma = (gamma < 0.0) ? (-gamma + 1.0) : (1.0 / (gamma + 1.0)); - vec3_set(&filter->gamma, (float)gamma, (float)gamma, (float)gamma); + filter->gamma = (float)gamma; /* Build our contrast number. */ - filter->contrast = + float contrast = (float)obs_data_get_double(settings, SETTING_CONTRAST) + 1.0f; - float one_minus_con = (1.0f - filter->contrast) / 2.0f; + float one_minus_con = (1.0f - contrast) / 2.0f; /* Now let's build our Contrast matrix. */ - filter->con_matrix = (struct matrix4){filter->contrast, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - filter->contrast, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - filter->contrast, - 0.0f, - one_minus_con, - one_minus_con, - one_minus_con, - 1.0f}; + filter->con_matrix = (struct matrix4){ + contrast, 0.0f, 0.0f, 0.0f, + 0.0f, contrast, 0.0f, 0.0f, + 0.0f, 0.0f, contrast, 0.0f, + one_minus_con, one_minus_con, one_minus_con, 1.0f}; /* Build our brightness number. */ - filter->brightness = + float brightness = (float)obs_data_get_double(settings, SETTING_BRIGHTNESS); /* @@ -136,21 +131,21 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) * this matrix to the identity matrix, so now we only need * to set the 3 variables that have changed. */ - filter->bright_matrix.t.x = filter->brightness; - filter->bright_matrix.t.y = filter->brightness; - filter->bright_matrix.t.z = filter->brightness; + filter->bright_matrix.t.x = brightness; + filter->bright_matrix.t.y = brightness; + filter->bright_matrix.t.z = brightness; /* Build our Saturation number. */ - filter->saturation = + float saturation = (float)obs_data_get_double(settings, SETTING_SATURATION) + 1.0f; /* Factor in the selected color weights. */ - float one_minus_sat_red = (1.0f - filter->saturation) * red_weight; - float one_minus_sat_green = (1.0f - filter->saturation) * green_weight; - float one_minus_sat_blue = (1.0f - filter->saturation) * blue_weight; - float sat_val_red = one_minus_sat_red + filter->saturation; - float sat_val_green = one_minus_sat_green + filter->saturation; - float sat_val_blue = one_minus_sat_blue + filter->saturation; + float one_minus_sat_red = (1.0f - saturation) * red_weight; + float one_minus_sat_green = (1.0f - saturation) * green_weight; + float one_minus_sat_blue = (1.0f - saturation) * blue_weight; + float sat_val_red = one_minus_sat_red + saturation; + float sat_val_green = one_minus_sat_green + saturation; + float sat_val_blue = one_minus_sat_blue + saturation; /* Now we build our Saturation matrix. */ filter->sat_matrix = (struct matrix4){sat_val_red, @@ -171,57 +166,62 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) 1.0f}; /* Build our Hue number. */ - filter->hue_shift = + float hue_shift = (float)obs_data_get_double(settings, SETTING_HUESHIFT); /* Build our Transparency number. */ - filter->opacity = + float opacity = (float)obs_data_get_int(settings, SETTING_OPACITY) * 0.01f; /* Hue is the radian of 0 to 360 degrees. */ - float half_angle = 0.5f * (float)(filter->hue_shift / (180.0f / M_PI)); + float half_angle = 0.5f * (float)(hue_shift / (180.0f / M_PI)); /* Pseudo-Quaternion To Matrix. */ float rot_quad1 = root3 * (float)sin(half_angle); - vec3_set(&filter->rot_quaternion, rot_quad1, rot_quad1, rot_quad1); - filter->rot_quaternion_w = (float)cos(half_angle); + struct vec3 rot_quaternion; + vec3_set(&rot_quaternion, rot_quad1, rot_quad1, rot_quad1); + float rot_quaternion_w = (float)cos(half_angle); - vec3_mul(&filter->cross, &filter->rot_quaternion, - &filter->rot_quaternion); - vec3_mul(&filter->square, &filter->rot_quaternion, - &filter->rot_quaternion); - vec3_mulf(&filter->wimag, &filter->rot_quaternion, - filter->rot_quaternion_w); + struct vec3 cross; + vec3_mul(&cross, &rot_quaternion, &rot_quaternion); + struct vec3 square; + vec3_mul(&square, &rot_quaternion, &rot_quaternion); + struct vec3 wimag; + vec3_mulf(&wimag, &rot_quaternion, rot_quaternion_w); - vec3_mulf(&filter->square, &filter->square, 2.0f); - vec3_sub(&filter->diag, &filter->half_unit, &filter->square); - vec3_add(&filter->a_line, &filter->cross, &filter->wimag); - vec3_sub(&filter->b_line, &filter->cross, &filter->wimag); + vec3_mulf(&square, &square, 2.0f); + struct vec3 diag; + vec3_sub(&diag, &filter->half_unit, &square); + struct vec3 a_line; + vec3_add(&a_line, &cross, &wimag); + struct vec3 b_line; + vec3_sub(&b_line, &cross, &wimag); /* Now we build our Hue and Opacity matrix. */ - filter->hue_op_matrix = (struct matrix4){filter->diag.x * 2.0f, - filter->b_line.z * 2.0f, - filter->a_line.y * 2.0f, + filter->hue_op_matrix = (struct matrix4){diag.x * 2.0f, + b_line.z * 2.0f, + a_line.y * 2.0f, 0.0f, - filter->a_line.z * 2.0f, - filter->diag.y * 2.0f, - filter->b_line.x * 2.0f, + a_line.z * 2.0f, + diag.y * 2.0f, + b_line.x * 2.0f, 0.0f, - filter->b_line.y * 2.0f, - filter->a_line.x * 2.0f, - filter->diag.z * 2.0f, + b_line.y * 2.0f, + a_line.x * 2.0f, + diag.z * 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, - filter->opacity}; + opacity}; /* Now get the overlay color data. */ uint32_t color = (uint32_t)obs_data_get_int(settings, SETTING_COLOR); - vec4_from_rgba(&filter->color, color); + struct vec4 color_v4; + vec4_from_rgba(&color_v4, color); /* * Now let's build our Color 'overlay' matrix. @@ -229,13 +229,13 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) * this matrix to the identity matrix, so now we only need * to set the 6 variables that have changed. */ - filter->color_matrix.x.x = filter->color.x; - filter->color_matrix.y.y = filter->color.y; - filter->color_matrix.z.z = filter->color.z; + filter->color_matrix.x.x = color_v4.x; + filter->color_matrix.y.y = color_v4.y; + filter->color_matrix.z.z = color_v4.z; - filter->color_matrix.t.x = filter->color.w * filter->color.x; - filter->color_matrix.t.y = filter->color.w * filter->color.y; - filter->color_matrix.t.z = filter->color.w * filter->color.z; + filter->color_matrix.t.x = color_v4.w * color_v4.x; + filter->color_matrix.t.y = color_v4.w * color_v4.y; + filter->color_matrix.t.z = color_v4.w * color_v4.z; /* First we apply the Contrast & Brightness matrix. */ matrix4_mul(&filter->final_matrix, &filter->bright_matrix, @@ -251,12 +251,140 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) &filter->color_matrix); } +static void color_correction_filter_update_v2(void *data, obs_data_t *settings) +{ + struct color_correction_filter_data_v2 *filter = data; + + /* Build our Gamma numbers. */ + double gamma = obs_data_get_double(settings, SETTING_GAMMA); + gamma = (gamma < 0.0) ? (-gamma + 1.0) : (1.0 / (gamma + 1.0)); + filter->gamma = (float)gamma; + + /* Build our contrast number. */ + float contrast = (float)obs_data_get_double(settings, SETTING_CONTRAST); + contrast = (contrast < 0.0f) ? (1.0f / (-contrast + 1.0f)) + : (contrast + 1.0f); + + /* Now let's build our Contrast matrix. */ + filter->con_matrix = (struct matrix4){contrast, 0.0f, 0.0f, 0.0f, 0.0f, + contrast, 0.0f, 0.0f, 0.0f, 0.0f, + contrast, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f}; + + /* Build our brightness number. */ + float brightness = + (float)obs_data_get_double(settings, SETTING_BRIGHTNESS); + + /* + * Now let's build our Brightness matrix. + * Earlier (in the function color_correction_filter_create) we set + * this matrix to the identity matrix, so now we only need + * to set the 3 variables that have changed. + */ + filter->bright_matrix.t.x = brightness; + filter->bright_matrix.t.y = brightness; + filter->bright_matrix.t.z = brightness; + + /* Build our Saturation number. */ + float saturation = + (float)obs_data_get_double(settings, SETTING_SATURATION) + 1.0f; + + /* Factor in the selected color weights. */ + float one_minus_sat_red = (1.0f - saturation) * red_weight; + float one_minus_sat_green = (1.0f - saturation) * green_weight; + float one_minus_sat_blue = (1.0f - saturation) * blue_weight; + float sat_val_red = one_minus_sat_red + saturation; + float sat_val_green = one_minus_sat_green + saturation; + float sat_val_blue = one_minus_sat_blue + saturation; + + /* Now we build our Saturation matrix. */ + filter->sat_matrix = (struct matrix4){sat_val_red, + one_minus_sat_red, + one_minus_sat_red, + 0.0f, + one_minus_sat_green, + sat_val_green, + one_minus_sat_green, + 0.0f, + one_minus_sat_blue, + one_minus_sat_blue, + sat_val_blue, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f}; + + /* Build our Hue number. */ + float hue_shift = + (float)obs_data_get_double(settings, SETTING_HUESHIFT); + + /* Build our Transparency number. */ + float opacity = + (float)obs_data_get_int(settings, SETTING_OPACITY) * 0.01f; + + /* Hue is the radian of 0 to 360 degrees. */ + float half_angle = 0.5f * (float)(hue_shift / (180.0f / M_PI)); + + /* Pseudo-Quaternion To Matrix. */ + float rot_quad1 = root3 * (float)sin(half_angle); + struct vec3 rot_quaternion; + vec3_set(&rot_quaternion, rot_quad1, rot_quad1, rot_quad1); + float rot_quaternion_w = (float)cos(half_angle); + + struct vec3 cross; + vec3_mul(&cross, &rot_quaternion, &rot_quaternion); + struct vec3 square; + vec3_mul(&square, &rot_quaternion, &rot_quaternion); + struct vec3 wimag; + vec3_mulf(&wimag, &rot_quaternion, rot_quaternion_w); + + vec3_mulf(&square, &square, 2.0f); + struct vec3 diag; + vec3_sub(&diag, &filter->half_unit, &square); + struct vec3 a_line; + vec3_add(&a_line, &cross, &wimag); + struct vec3 b_line; + vec3_sub(&b_line, &cross, &wimag); + + /* Now we build our Hue and Opacity matrix. */ + filter->hue_op_matrix = (struct matrix4){diag.x * 2.0f, + b_line.z * 2.0f, + a_line.y * 2.0f, + 0.0f, + + a_line.z * 2.0f, + diag.y * 2.0f, + b_line.x * 2.0f, + 0.0f, + + b_line.y * 2.0f, + a_line.x * 2.0f, + diag.z * 2.0f, + 0.0f, + + 0.0f, + 0.0f, + 0.0f, + opacity}; + + /* First we apply the Contrast & Brightness matrix. */ + matrix4_mul(&filter->final_matrix, &filter->con_matrix, + &filter->bright_matrix); + /* Now we apply the Saturation matrix. */ + matrix4_mul(&filter->final_matrix, &filter->final_matrix, + &filter->sat_matrix); + /* Next we apply the Hue+Opacity matrix. */ + matrix4_mul(&filter->final_matrix, &filter->final_matrix, + &filter->hue_op_matrix); +} + /* * Since this is C we have to be careful when destroying/removing items from * OBS. Jim has added several useful functions to help keep memory leaks to * a minimum, and handle the destruction and construction of these filters. */ -static void color_correction_filter_destroy(void *data) +static void color_correction_filter_destroy_v1(void *data) { struct color_correction_filter_data *filter = data; @@ -269,14 +397,27 @@ static void color_correction_filter_destroy(void *data) bfree(data); } +static void color_correction_filter_destroy_v2(void *data) +{ + struct color_correction_filter_data_v2 *filter = data; + + if (filter->effect) { + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + obs_leave_graphics(); + } + + bfree(data); +} + /* * When you apply a filter OBS creates it, and adds it to the source. OBS also * starts rendering it immediately. This function doesn't just 'create' the * filter, it also calls the render function (farther below) that contains the * actual rendering code. */ -static void *color_correction_filter_create(obs_data_t *settings, - obs_source_t *context) +static void *color_correction_filter_create_v1(obs_data_t *settings, + obs_source_t *context) { /* * Because of limitations of pre-c99 compilers, you can't create an @@ -324,7 +465,7 @@ static void *color_correction_filter_create(obs_data_t *settings, * values that don't exist anymore. */ if (!filter->effect) { - color_correction_filter_destroy(filter); + color_correction_filter_destroy_v1(filter); return NULL; } @@ -333,12 +474,73 @@ static void *color_correction_filter_create(obs_data_t *settings, * we could end up with the user controlled sliders and values * updating, but the visuals not updating to match. */ - color_correction_filter_update(filter, settings); + color_correction_filter_update_v1(filter, settings); + return filter; +} + +static void *color_correction_filter_create_v2(obs_data_t *settings, + obs_source_t *context) +{ + /* + * Because of limitations of pre-c99 compilers, you can't create an + * array that doesn't have a known size at compile time. The below + * function calculates the size needed and allocates memory to + * handle the source. + */ + struct color_correction_filter_data_v2 *filter = + bzalloc(sizeof(struct color_correction_filter_data_v2)); + + /* + * By default the effect file is stored in the ./data directory that + * your filter resides in. + */ + char *effect_path = obs_module_file("color_correction_filter.effect"); + + filter->context = context; + + /* Set/clear/assign for all necessary vectors. */ + vec3_set(&filter->half_unit, 0.5f, 0.5f, 0.5f); + matrix4_identity(&filter->bright_matrix); + + /* Here we enter the GPU drawing/shader portion of our code. */ + obs_enter_graphics(); + + /* Load the shader on the GPU. */ + filter->effect = gs_effect_create_from_file(effect_path, NULL); + + /* If the filter is active pass the parameters to the filter. */ + if (filter->effect) { + filter->gamma_param = gs_effect_get_param_by_name( + filter->effect, SETTING_GAMMA); + filter->final_matrix_param = gs_effect_get_param_by_name( + filter->effect, "color_matrix"); + } + + obs_leave_graphics(); + + bfree(effect_path); + + /* + * If the filter has been removed/deactivated, destroy the filter + * and exit out so we don't crash OBS by telling it to update + * values that don't exist anymore. + */ + if (!filter->effect) { + color_correction_filter_destroy_v2(filter); + return NULL; + } + + /* + * It's important to call the update function here. If we don't + * we could end up with the user controlled sliders and values + * updating, but the visuals not updating to match. + */ + color_correction_filter_update_v2(filter, settings); return filter; } /* This is where the actual rendering of the filter takes place. */ -static void color_correction_filter_render(void *data, gs_effect_t *effect) +static void color_correction_filter_render_v1(void *data, gs_effect_t *effect) { struct color_correction_filter_data *filter = data; @@ -347,7 +549,7 @@ static void color_correction_filter_render(void *data, gs_effect_t *effect) return; /* Now pass the interface variables to the .effect file. */ - gs_effect_set_vec3(filter->gamma_param, &filter->gamma); + gs_effect_set_float(filter->gamma_param, filter->gamma); gs_effect_set_matrix4(filter->final_matrix_param, &filter->final_matrix); @@ -356,13 +558,33 @@ static void color_correction_filter_render(void *data, gs_effect_t *effect) UNUSED_PARAMETER(effect); } +static void color_correction_filter_render_v2(void *data, gs_effect_t *effect) +{ + struct color_correction_filter_data_v2 *filter = data; + + if (!obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_ALLOW_DIRECT_RENDERING)) + return; + + /* Now pass the interface variables to the .effect file. */ + gs_effect_set_float(filter->gamma_param, filter->gamma); + gs_effect_set_matrix4(filter->final_matrix_param, + &filter->final_matrix); + + const bool previous = gs_set_linear_srgb(true); + obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + gs_set_linear_srgb(previous); + + UNUSED_PARAMETER(effect); +} + /* * This function sets the interface. the types (add_*_Slider), the type of * data collected (int), the internal name, user-facing name, minimum, * maximum and step values. While a custom interface can be built, for a * simple filter like this it's better to use the supplied functions. */ -static obs_properties_t *color_correction_filter_properties(void *data) +static obs_properties_t *color_correction_filter_properties_v1(void *data) { obs_properties_t *props = obs_properties_create(); @@ -386,6 +608,28 @@ static obs_properties_t *color_correction_filter_properties(void *data) return props; } +static obs_properties_t *color_correction_filter_properties_v2(void *data) +{ + obs_properties_t *props = obs_properties_create(); + + obs_properties_add_float_slider(props, SETTING_GAMMA, TEXT_GAMMA, -3.0, + 3.0, 0.01); + + obs_properties_add_float_slider(props, SETTING_CONTRAST, TEXT_CONTRAST, + -4.0, 4.0, 0.01); + obs_properties_add_float_slider(props, SETTING_BRIGHTNESS, + TEXT_BRIGHTNESS, -1.0, 1.0, 0.01); + obs_properties_add_float_slider(props, SETTING_SATURATION, + TEXT_SATURATION, -1.0, 5.0, 0.01); + obs_properties_add_float_slider(props, SETTING_HUESHIFT, TEXT_HUESHIFT, + -180.0, 180.0, 0.01); + obs_properties_add_int_slider(props, SETTING_OPACITY, TEXT_OPACITY, 0, + 100, 1); + + UNUSED_PARAMETER(data); + return props; +} + /* * As the functions' namesake, this provides the default settings for any * options you wish to provide a default for. Try to select defaults that @@ -393,17 +637,27 @@ static obs_properties_t *color_correction_filter_properties(void *data) * *NOTE* this function is completely optional, as is providing a default * for any particular setting. */ -static void color_correction_filter_defaults(obs_data_t *settings) +static void color_correction_filter_defaults_v1(obs_data_t *settings) { obs_data_set_default_double(settings, SETTING_GAMMA, 0.0); obs_data_set_default_double(settings, SETTING_CONTRAST, 0.0); obs_data_set_default_double(settings, SETTING_BRIGHTNESS, 0.0); obs_data_set_default_double(settings, SETTING_SATURATION, 0.0); obs_data_set_default_double(settings, SETTING_HUESHIFT, 0.0); - obs_data_set_default_double(settings, SETTING_OPACITY, 100.0); + obs_data_set_default_int(settings, SETTING_OPACITY, 100); obs_data_set_default_int(settings, SETTING_COLOR, 0x00FFFFFF); } +static void color_correction_filter_defaults_v2(obs_data_t *settings) +{ + obs_data_set_default_double(settings, SETTING_GAMMA, 0.0); + obs_data_set_default_double(settings, SETTING_CONTRAST, 0.0); + obs_data_set_default_double(settings, SETTING_BRIGHTNESS, 0.0); + obs_data_set_default_double(settings, SETTING_SATURATION, 0.0); + obs_data_set_default_double(settings, SETTING_HUESHIFT, 0.0); + obs_data_set_default_int(settings, SETTING_OPACITY, 100); +} + /* * So how does OBS keep track of all these plug-ins/filters? How does OBS know * which function to call when it needs to update a setting? Or a source? Or @@ -417,12 +671,26 @@ static void color_correction_filter_defaults(obs_data_t *settings) struct obs_source_info color_filter = { .id = "color_filter", .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, + .get_name = color_correction_filter_name, + .create = color_correction_filter_create_v1, + .destroy = color_correction_filter_destroy_v1, + .video_render = color_correction_filter_render_v1, + .update = color_correction_filter_update_v1, + .get_properties = color_correction_filter_properties_v1, + .get_defaults = color_correction_filter_defaults_v1, +}; + +struct obs_source_info color_filter_v2 = { + .id = "color_filter", + .version = 2, + .type = OBS_SOURCE_TYPE_FILTER, .output_flags = OBS_SOURCE_VIDEO, .get_name = color_correction_filter_name, - .create = color_correction_filter_create, - .destroy = color_correction_filter_destroy, - .video_render = color_correction_filter_render, - .update = color_correction_filter_update, - .get_properties = color_correction_filter_properties, - .get_defaults = color_correction_filter_defaults, + .create = color_correction_filter_create_v2, + .destroy = color_correction_filter_destroy_v2, + .video_render = color_correction_filter_render_v2, + .update = color_correction_filter_update_v2, + .get_properties = color_correction_filter_properties_v2, + .get_defaults = color_correction_filter_defaults_v2, }; diff --git a/plugins/obs-filters/color-grade-filter.c b/plugins/obs-filters/color-grade-filter.c index 8d814e69b..d7849063d 100644 --- a/plugins/obs-filters/color-grade-filter.c +++ b/plugins/obs-filters/color-grade-filter.c @@ -452,8 +452,10 @@ static void color_grade_filter_render(void *data, gs_effect_t *effect) param = gs_effect_get_param_by_name(filter->effect, "cube_width_i"); gs_effect_set_float(param, 1.0f / filter->cube_width); + const bool previous = gs_set_linear_srgb(true); obs_source_process_filter_tech_end(filter->context, filter->effect, 0, 0, tech_name); + gs_set_linear_srgb(previous); UNUSED_PARAMETER(effect); } diff --git a/plugins/obs-filters/color-key-filter.c b/plugins/obs-filters/color-key-filter.c index 6dbdf2d45..c97d67136 100644 --- a/plugins/obs-filters/color-key-filter.c +++ b/plugins/obs-filters/color-key-filter.c @@ -49,14 +49,39 @@ struct color_key_filter_data { float smoothness; }; +struct color_key_filter_data_v2 { + obs_source_t *context; + + gs_effect_t *effect; + + gs_eparam_t *opacity_param; + gs_eparam_t *contrast_param; + gs_eparam_t *brightness_param; + gs_eparam_t *gamma_param; + + gs_eparam_t *key_color_param; + gs_eparam_t *similarity_param; + gs_eparam_t *smoothness_param; + + float opacity; + float contrast; + float brightness; + float gamma; + + struct vec4 key_color; + float similarity; + float smoothness; +}; + static const char *color_key_name(void *unused) { UNUSED_PARAMETER(unused); return obs_module_text("ColorKeyFilter"); } -static inline void color_settings_update(struct color_key_filter_data *filter, - obs_data_t *settings) +static inline void +color_settings_update_v1(struct color_key_filter_data *filter, + obs_data_t *settings) { uint32_t opacity = (uint32_t)obs_data_get_int(settings, SETTING_OPACITY); @@ -79,8 +104,28 @@ static inline void color_settings_update(struct color_key_filter_data *filter, vec4_from_rgba(&filter->color, color); } -static inline void key_settings_update(struct color_key_filter_data *filter, - obs_data_t *settings) +static inline void +color_settings_update_v2(struct color_key_filter_data_v2 *filter, + obs_data_t *settings) +{ + filter->opacity = + (float)obs_data_get_int(settings, SETTING_OPACITY) / 100.0f; + + double contrast = obs_data_get_double(settings, SETTING_CONTRAST); + contrast = (contrast < 0.0) ? (1.0 / (-contrast + 1.0)) + : (contrast + 1.0); + filter->contrast = (float)contrast; + + filter->brightness = + (float)obs_data_get_double(settings, SETTING_BRIGHTNESS); + + double gamma = obs_data_get_double(settings, SETTING_GAMMA); + gamma = (gamma < 0.0) ? (-gamma + 1.0) : (1.0 / (gamma + 1.0)); + filter->gamma = (float)gamma; +} + +static inline void key_settings_update_v1(struct color_key_filter_data *filter, + obs_data_t *settings) { int64_t similarity = obs_data_get_int(settings, SETTING_SIMILARITY); int64_t smoothness = obs_data_get_int(settings, SETTING_SMOOTHNESS); @@ -104,15 +149,49 @@ static inline void key_settings_update(struct color_key_filter_data *filter, filter->smoothness = (float)smoothness / 1000.0f; } -static void color_key_update(void *data, obs_data_t *settings) +static inline void +key_settings_update_v2(struct color_key_filter_data_v2 *filter, + obs_data_t *settings) +{ + int64_t similarity = obs_data_get_int(settings, SETTING_SIMILARITY); + int64_t smoothness = obs_data_get_int(settings, SETTING_SMOOTHNESS); + uint32_t key_color = + (uint32_t)obs_data_get_int(settings, SETTING_KEY_COLOR); + const char *key_type = + obs_data_get_string(settings, SETTING_COLOR_TYPE); + + if (strcmp(key_type, "green") == 0) + key_color = 0x00FF00; + else if (strcmp(key_type, "blue") == 0) + key_color = 0xFF0000; + else if (strcmp(key_type, "red") == 0) + key_color = 0x0000FF; + else if (strcmp(key_type, "magenta") == 0) + key_color = 0xFF00FF; + + vec4_from_rgba(&filter->key_color, key_color | 0xFF000000); + + filter->similarity = (float)similarity / 1000.0f; + filter->smoothness = (float)smoothness / 1000.0f; +} + +static void color_key_update_v1(void *data, obs_data_t *settings) { struct color_key_filter_data *filter = data; - color_settings_update(filter, settings); - key_settings_update(filter, settings); + color_settings_update_v1(filter, settings); + key_settings_update_v1(filter, settings); } -static void color_key_destroy(void *data) +static void color_key_update_v2(void *data, obs_data_t *settings) +{ + struct color_key_filter_data_v2 *filter = data; + + color_settings_update_v2(filter, settings); + key_settings_update_v2(filter, settings); +} + +static void color_key_destroy_v1(void *data) { struct color_key_filter_data *filter = data; @@ -125,7 +204,20 @@ static void color_key_destroy(void *data) bfree(data); } -static void *color_key_create(obs_data_t *settings, obs_source_t *context) +static void color_key_destroy_v2(void *data) +{ + struct color_key_filter_data_v2 *filter = data; + + if (filter->effect) { + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + obs_leave_graphics(); + } + + bfree(data); +} + +static void *color_key_create_v1(obs_data_t *settings, obs_source_t *context) { struct color_key_filter_data *filter = bzalloc(sizeof(struct color_key_filter_data)); @@ -158,15 +250,56 @@ static void *color_key_create(obs_data_t *settings, obs_source_t *context) bfree(effect_path); if (!filter->effect) { - color_key_destroy(filter); + color_key_destroy_v1(filter); return NULL; } - color_key_update(filter, settings); + color_key_update_v1(filter, settings); return filter; } -static void color_key_render(void *data, gs_effect_t *effect) +static void *color_key_create_v2(obs_data_t *settings, obs_source_t *context) +{ + struct color_key_filter_data_v2 *filter = + bzalloc(sizeof(struct color_key_filter_data_v2)); + char *effect_path = obs_module_file("color_key_filter_v2.effect"); + + filter->context = context; + + obs_enter_graphics(); + + filter->effect = gs_effect_create_from_file(effect_path, NULL); + if (filter->effect) { + filter->opacity_param = + gs_effect_get_param_by_name(filter->effect, "opacity"); + filter->contrast_param = + gs_effect_get_param_by_name(filter->effect, "contrast"); + filter->brightness_param = gs_effect_get_param_by_name( + filter->effect, "brightness"); + filter->gamma_param = + gs_effect_get_param_by_name(filter->effect, "gamma"); + filter->key_color_param = gs_effect_get_param_by_name( + filter->effect, "key_color"); + filter->similarity_param = gs_effect_get_param_by_name( + filter->effect, "similarity"); + filter->smoothness_param = gs_effect_get_param_by_name( + filter->effect, "smoothness"); + } + + obs_leave_graphics(); + + bfree(effect_path); + + if (!filter->effect) { + color_key_destroy_v2(filter); + return NULL; + } + + color_key_update_v2(filter, settings); + return filter; +} + +static void color_key_render_v1(void *data, gs_effect_t *effect) { struct color_key_filter_data *filter = data; @@ -187,6 +320,29 @@ static void color_key_render(void *data, gs_effect_t *effect) UNUSED_PARAMETER(effect); } +static void color_key_render_v2(void *data, gs_effect_t *effect) +{ + struct color_key_filter_data_v2 *filter = data; + + if (!obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_ALLOW_DIRECT_RENDERING)) + return; + + gs_effect_set_float(filter->opacity_param, filter->opacity); + gs_effect_set_float(filter->contrast_param, filter->contrast); + gs_effect_set_float(filter->brightness_param, filter->brightness); + gs_effect_set_float(filter->gamma_param, filter->gamma); + gs_effect_set_vec4(filter->key_color_param, &filter->key_color); + gs_effect_set_float(filter->similarity_param, filter->similarity); + gs_effect_set_float(filter->smoothness_param, filter->smoothness); + + const bool previous = gs_set_linear_srgb(true); + obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + gs_set_linear_srgb(previous); + + UNUSED_PARAMETER(effect); +} + static bool key_type_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings) { @@ -200,7 +356,7 @@ static bool key_type_changed(obs_properties_t *props, obs_property_t *p, return true; } -static obs_properties_t *color_key_properties(void *data) +static obs_properties_t *color_key_properties_v1(void *data) { obs_properties_t *props = obs_properties_create(); @@ -236,6 +392,42 @@ static obs_properties_t *color_key_properties(void *data) return props; } +static obs_properties_t *color_key_properties_v2(void *data) +{ + obs_properties_t *props = obs_properties_create(); + + obs_property_t *p = obs_properties_add_list(props, SETTING_COLOR_TYPE, + TEXT_COLOR_TYPE, + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, obs_module_text("Green"), "green"); + obs_property_list_add_string(p, obs_module_text("Blue"), "blue"); + obs_property_list_add_string(p, obs_module_text("Red"), "red"); + obs_property_list_add_string(p, obs_module_text("Magenta"), "magenta"); + obs_property_list_add_string(p, obs_module_text("CustomColor"), + "custom"); + + obs_property_set_modified_callback(p, key_type_changed); + + obs_properties_add_color(props, SETTING_KEY_COLOR, TEXT_KEY_COLOR); + obs_properties_add_int_slider(props, SETTING_SIMILARITY, + TEXT_SIMILARITY, 1, 1000, 1); + obs_properties_add_int_slider(props, SETTING_SMOOTHNESS, + TEXT_SMOOTHNESS, 1, 1000, 1); + + obs_properties_add_int_slider(props, SETTING_OPACITY, TEXT_OPACITY, 0, + 100, 1); + obs_properties_add_float_slider(props, SETTING_CONTRAST, TEXT_CONTRAST, + -4.0, 4.0, 0.01); + obs_properties_add_float_slider(props, SETTING_BRIGHTNESS, + TEXT_BRIGHTNESS, -1.0, 1.0, 0.01); + obs_properties_add_float_slider(props, SETTING_GAMMA, TEXT_GAMMA, -1.0, + 1.0, 0.01); + + UNUSED_PARAMETER(data); + return props; +} + static void color_key_defaults(obs_data_t *settings) { obs_data_set_default_int(settings, SETTING_OPACITY, 100); @@ -251,12 +443,26 @@ static void color_key_defaults(obs_data_t *settings) struct obs_source_info color_key_filter = { .id = "color_key_filter", .type = OBS_SOURCE_TYPE_FILTER, - .output_flags = OBS_SOURCE_VIDEO, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, .get_name = color_key_name, - .create = color_key_create, - .destroy = color_key_destroy, - .video_render = color_key_render, - .update = color_key_update, - .get_properties = color_key_properties, + .create = color_key_create_v1, + .destroy = color_key_destroy_v1, + .video_render = color_key_render_v1, + .update = color_key_update_v1, + .get_properties = color_key_properties_v1, + .get_defaults = color_key_defaults, +}; + +struct obs_source_info color_key_filter_v2 = { + .id = "color_key_filter", + .version = 2, + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = color_key_name, + .create = color_key_create_v2, + .destroy = color_key_destroy_v2, + .video_render = color_key_render_v2, + .update = color_key_update_v2, + .get_properties = color_key_properties_v2, .get_defaults = color_key_defaults, }; diff --git a/plugins/obs-filters/data/chroma_key_filter_v2.effect b/plugins/obs-filters/data/chroma_key_filter_v2.effect new file mode 100644 index 000000000..dd0439684 --- /dev/null +++ b/plugins/obs-filters/data/chroma_key_filter_v2.effect @@ -0,0 +1,97 @@ +uniform float4x4 ViewProj; +uniform texture2d image; + +uniform float4 cb_v4 = { -0.100644, -0.338572, 0.439216, 0.501961 }; +uniform float4 cr_v4 = { 0.439216, -0.398942, -0.040274, 0.501961 }; + +uniform float opacity; +uniform float contrast; +uniform float brightness; +uniform float gamma; + +uniform float2 chroma_key; +uniform float2 pixel_size; +uniform float similarity; +uniform float smoothness; +uniform float spill; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 CalcColor(float4 rgba) +{ + return float4(pow(rgba.rgb, gamma) * contrast + brightness, rgba.a); +} + +float GetChromaDist(float3 rgb) +{ + float cb = dot(rgb.rgb, cb_v4.xyz) + cb_v4.w; + float cr = dot(rgb.rgb, cr_v4.xyz) + cr_v4.w; + return distance(chroma_key, float2(cr, cb)); +} + +float4 SampleTexture(float2 uv) +{ + return image.Sample(textureSampler, uv); +} + +float GetBoxFilteredChromaDist(float3 rgb, float2 texCoord) +{ + float2 h_pixel_size = pixel_size / 2.0; + float2 point_0 = float2(pixel_size.x, h_pixel_size.y); + float2 point_1 = float2(h_pixel_size.x, -pixel_size.y); + float distVal = GetChromaDist(SampleTexture(texCoord-point_0).rgb); + distVal += GetChromaDist(SampleTexture(texCoord+point_0).rgb); + distVal += GetChromaDist(SampleTexture(texCoord-point_1).rgb); + distVal += GetChromaDist(SampleTexture(texCoord+point_1).rgb); + distVal *= 2.0; + distVal += GetChromaDist(rgb); + return distVal / 9.0; +} + +float4 ProcessChromaKey(float4 rgba, VertData v_in) +{ + float chromaDist = GetBoxFilteredChromaDist(rgba.rgb, v_in.uv); + float baseMask = chromaDist - similarity; + float fullMask = pow(saturate(baseMask / smoothness), 1.5); + float spillVal = pow(saturate(baseMask / spill), 1.5); + + rgba.a *= opacity; + rgba.a *= fullMask; + + float desat = dot(rgba.rgb, float3(0.2126, 0.7152, 0.0722)); + rgba.rgb = lerp(float3(desat, desat, desat), rgba.rgb, spillVal); + + return CalcColor(rgba); +} + +float4 PSChromaKeyRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv); + return ProcessChromaKey(rgba, v_in); +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSChromaKeyRGBA(v_in); + } +} diff --git a/plugins/obs-filters/data/color_correction_filter.effect b/plugins/obs-filters/data/color_correction_filter.effect index 7d599b683..d1f65a7a0 100644 --- a/plugins/obs-filters/data/color_correction_filter.effect +++ b/plugins/obs-filters/data/color_correction_filter.effect @@ -18,7 +18,7 @@ along with this program. If not, see . uniform float4x4 ViewProj; uniform texture2d image; -uniform float3 gamma; +uniform float gamma; /* Pre-Compute variables. */ uniform float4x4 color_matrix; diff --git a/plugins/obs-filters/data/color_grade_filter.effect b/plugins/obs-filters/data/color_grade_filter.effect index 426009de1..ba05bc409 100644 --- a/plugins/obs-filters/data/color_grade_filter.effect +++ b/plugins/obs-filters/data/color_grade_filter.effect @@ -35,9 +35,29 @@ VertDataOut VSDefault(VertDataIn v_in) return vert_out; } +float srgb_linear_to_nonlinear_channel(float u) +{ + return (u <= 0.0031308) ? (12.92 * u) : ((1.055 * pow(u, 1.0 / 2.4)) - 0.055); +} + +float4 srgb_linear_to_nonlinear(float4 v) +{ + return float4(srgb_linear_to_nonlinear_channel(v.r), srgb_linear_to_nonlinear_channel(v.g), srgb_linear_to_nonlinear_channel(v.b), v.a); +} + +float srgb_nonlinear_to_linear_channel(float u) +{ + return (u <= 0.04045) ? (u / 12.92) : pow((u + 0.055) / 1.055, 2.4); +} + +float4 srgb_nonlinear_to_linear(float4 v) +{ + return float4(srgb_nonlinear_to_linear_channel(v.r), srgb_nonlinear_to_linear_channel(v.g), srgb_nonlinear_to_linear_channel(v.b), v.a); +} + float4 LUT1D(VertDataOut v_in) : TARGET { - float4 textureColor = image.Sample(textureSampler, v_in.uv); + float4 textureColor = srgb_linear_to_nonlinear(image.Sample(textureSampler, v_in.uv)); if (textureColor.r >= domain_min.r && textureColor.r <= domain_max.r) { float u = textureColor.r * clut_scale.r + clut_offset.r; @@ -57,12 +77,12 @@ float4 LUT1D(VertDataOut v_in) : TARGET textureColor.b = lerp(textureColor.b, channel, clut_amount); } - return textureColor; + return srgb_nonlinear_to_linear(textureColor); } float4 LUT3D(VertDataOut v_in) : TARGET { - float4 textureColor = image.Sample(textureSampler, v_in.uv); + float4 textureColor = srgb_linear_to_nonlinear(image.Sample(textureSampler, v_in.uv)); float r = textureColor.r; float g = textureColor.g; float b = textureColor.b; @@ -145,7 +165,7 @@ float4 LUT3D(VertDataOut v_in) : TARGET textureColor.rgb = lerp(textureColor.rgb, luttedColor, clut_amount); } - return textureColor; + return srgb_nonlinear_to_linear(textureColor); } technique Draw1D diff --git a/plugins/obs-filters/data/color_key_filter_v2.effect b/plugins/obs-filters/data/color_key_filter_v2.effect new file mode 100644 index 000000000..df6e155aa --- /dev/null +++ b/plugins/obs-filters/data/color_key_filter_v2.effect @@ -0,0 +1,64 @@ +uniform float4x4 ViewProj; +uniform texture2d image; + +uniform float opacity; +uniform float contrast; +uniform float brightness; +uniform float gamma; + +uniform float4 key_color; +uniform float similarity; +uniform float smoothness; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 CalcColor(float4 rgba) +{ + return float4(pow(rgba.rgb, gamma) * contrast + brightness, rgba.a); +} + +float GetColorDist(float3 rgb) +{ + return distance(key_color.rgb, rgb); +} + +float4 ProcessColorKey(float4 rgba, VertData v_in) +{ + float colorDist = GetColorDist(rgba.rgb); + rgba.a *= saturate(max(colorDist - similarity, 0.0) / smoothness); + + return CalcColor(rgba); +} + +float4 PSColorKeyRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv); + rgba.a *= opacity; + return ProcessColorKey(rgba, v_in); +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSColorKeyRGBA(v_in); + } +} diff --git a/plugins/obs-filters/data/luma_key_filter_v2.effect b/plugins/obs-filters/data/luma_key_filter_v2.effect new file mode 100644 index 000000000..c1f8bcfd0 --- /dev/null +++ b/plugins/obs-filters/data/luma_key_filter_v2.effect @@ -0,0 +1,51 @@ +uniform float4x4 ViewProj; +uniform texture2d image; + +uniform float lumaMax; +uniform float lumaMin; +uniform float lumaMaxSmooth; +uniform float lumaMinSmooth; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PSALumaKeyRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv); + + float3 lumaCoef = float3(0.2126, 0.7152, 0.0722); + + float luminance = dot(rgba.rgb, lumaCoef); + + float clo = smoothstep(lumaMin, lumaMin + lumaMinSmooth, luminance); + float chi = 1. - smoothstep(lumaMax - lumaMaxSmooth, lumaMax, luminance); + + float amask = clo * chi; + + return float4(rgba.rgb, rgba.a * amask); +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSALumaKeyRGBA(v_in); + } +} diff --git a/plugins/obs-filters/data/sharpness.effect b/plugins/obs-filters/data/sharpness.effect index ea88e1744..d23a2fd56 100644 --- a/plugins/obs-filters/data/sharpness.effect +++ b/plugins/obs-filters/data/sharpness.effect @@ -4,8 +4,6 @@ uniform float4x4 ViewProj; uniform texture2d image; -uniform texture2d target; - uniform float sharpness; uniform float texture_width; uniform float texture_height; diff --git a/plugins/obs-filters/gpu-delay.c b/plugins/obs-filters/gpu-delay.c index e8f14e28d..4af8fdb9f 100644 --- a/plugins/obs-filters/gpu-delay.c +++ b/plugins/obs-filters/gpu-delay.c @@ -195,12 +195,22 @@ static void draw_frame(struct gpu_delay_filter_data *f) gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); gs_texture_t *tex = gs_texrender_get_texture(frame.render); if (tex) { + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture(image, tex); + if (linear_srgb) + gs_effect_set_texture_srgb(image, tex); + else + gs_effect_set_texture(image, tex); while (gs_effect_loop(effect, "Draw")) gs_draw_sprite(tex, 0, f->cx, f->cy); + + gs_enable_framebuffer_srgb(previous); } } diff --git a/plugins/obs-filters/luma-key-filter.c b/plugins/obs-filters/luma-key-filter.c index bdd9b2edf..d13fd6de4 100644 --- a/plugins/obs-filters/luma-key-filter.c +++ b/plugins/obs-filters/luma-key-filter.c @@ -66,11 +66,13 @@ static void luma_key_destroy(void *data) bfree(data); } -static void *luma_key_create(obs_data_t *settings, obs_source_t *context) +static void *luma_key_create_internal(obs_data_t *settings, + obs_source_t *context, + const char *effect_name) { struct luma_key_filter_data *filter = bzalloc(sizeof(struct luma_key_filter_data)); - char *effect_path = obs_module_file("luma_key_filter.effect"); + char *effect_path = obs_module_file(effect_name); filter->context = context; @@ -101,7 +103,19 @@ static void *luma_key_create(obs_data_t *settings, obs_source_t *context) return filter; } -static void luma_key_render(void *data, gs_effect_t *effect) +static void *luma_key_create_v1(obs_data_t *settings, obs_source_t *context) +{ + return luma_key_create_internal(settings, context, + "luma_key_filter.effect"); +} + +static void *luma_key_create_v2(obs_data_t *settings, obs_source_t *context) +{ + return luma_key_create_internal(settings, context, + "luma_key_filter_v2.effect"); +} + +static void luma_key_render_internal(void *data, bool srgb) { struct luma_key_filter_data *filter = data; @@ -116,9 +130,19 @@ static void luma_key_render(void *data, gs_effect_t *effect) gs_effect_set_float(filter->luma_min_smooth_param, filter->luma_min_smooth); + const bool previous = gs_set_linear_srgb(srgb); obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + gs_set_linear_srgb(previous); +} - UNUSED_PARAMETER(effect); +static void luma_key_render_v1(void *data, gs_effect_t *effect) +{ + luma_key_render_internal(data, false); +} + +static void luma_key_render_v2(void *data, gs_effect_t *effect) +{ + luma_key_render_internal(data, true); } static obs_properties_t *luma_key_properties(void *data) @@ -149,11 +173,25 @@ static void luma_key_defaults(obs_data_t *settings) struct obs_source_info luma_key_filter = { .id = "luma_key_filter", .type = OBS_SOURCE_TYPE_FILTER, - .output_flags = OBS_SOURCE_VIDEO, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, .get_name = luma_key_name, - .create = luma_key_create, + .create = luma_key_create_v1, .destroy = luma_key_destroy, - .video_render = luma_key_render, + .video_render = luma_key_render_v1, + .update = luma_key_update, + .get_properties = luma_key_properties, + .get_defaults = luma_key_defaults, +}; + +struct obs_source_info luma_key_filter_v2 = { + .id = "luma_key_filter", + .version = 2, + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = luma_key_name, + .create = luma_key_create_v2, + .destroy = luma_key_destroy, + .video_render = luma_key_render_v2, .update = luma_key_update, .get_properties = luma_key_properties, .get_defaults = luma_key_defaults, diff --git a/plugins/obs-filters/mask-filter.c b/plugins/obs-filters/mask-filter.c index 636eb3518..c9f6bcab1 100644 --- a/plugins/obs-filters/mask-filter.c +++ b/plugins/obs-filters/mask-filter.c @@ -75,12 +75,13 @@ static void mask_filter_image_load(struct mask_filter_data *filter) obs_enter_graphics(); gs_image_file_init_texture(&filter->image); obs_leave_graphics(); - - filter->target = filter->image.texture; } + + filter->target = filter->image.texture; } -static void mask_filter_update(void *data, obs_data_t *settings) +static void mask_filter_update_internal(void *data, obs_data_t *settings, + bool srgb) { struct mask_filter_data *filter = data; @@ -97,7 +98,10 @@ static void mask_filter_update(void *data, obs_data_t *settings) color &= 0xFFFFFF; color |= (uint32_t)(((double)opacity) * 2.55) << 24; - vec4_from_rgba(&filter->color, color); + if (srgb) + vec4_from_rgba_srgb(&filter->color, color); + else + vec4_from_rgba(&filter->color, color); mask_filter_image_load(filter); filter->lock_aspect = !obs_data_get_bool(settings, SETTING_STRETCH); @@ -111,6 +115,16 @@ static void mask_filter_update(void *data, obs_data_t *settings) obs_leave_graphics(); } +static void mask_filter_update_v1(void *data, obs_data_t *settings) +{ + mask_filter_update_internal(data, settings, false); +} + +static void mask_filter_update_v2(void *data, obs_data_t *settings) +{ + mask_filter_update_internal(data, settings, true); +} + static void mask_filter_defaults(obs_data_t *settings) { obs_data_set_default_string(settings, SETTING_TYPE, @@ -220,7 +234,7 @@ static void mask_filter_tick(void *data, float seconds) } } -static void mask_filter_render(void *data, gs_effect_t *effect) +static void mask_filter_render_internal(void *data, bool srgb) { struct mask_filter_data *filter = data; obs_source_t *target = obs_filter_get_target(filter->context); @@ -279,7 +293,21 @@ static void mask_filter_render(void *data, gs_effect_t *effect) param = gs_effect_get_param_by_name(filter->effect, "add_val"); gs_effect_set_vec2(param, &add_val); + const bool previous = gs_set_linear_srgb(srgb); obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + gs_set_linear_srgb(previous); +} + +static void mask_filter_render_v1(void *data, gs_effect_t *effect) +{ + mask_filter_render_internal(data, false); + + UNUSED_PARAMETER(effect); +} + +static void mask_filter_render_v2(void *data, gs_effect_t *effect) +{ + mask_filter_render_internal(data, true); UNUSED_PARAMETER(effect); } @@ -287,13 +315,28 @@ static void mask_filter_render(void *data, gs_effect_t *effect) struct obs_source_info mask_filter = { .id = "mask_filter", .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, + .get_name = mask_filter_get_name, + .create = mask_filter_create, + .destroy = mask_filter_destroy, + .update = mask_filter_update_v1, + .get_defaults = mask_filter_defaults, + .get_properties = mask_filter_properties, + .video_tick = mask_filter_tick, + .video_render = mask_filter_render_v1, +}; + +struct obs_source_info mask_filter_v2 = { + .id = "mask_filter", + .version = 2, + .type = OBS_SOURCE_TYPE_FILTER, .output_flags = OBS_SOURCE_VIDEO, .get_name = mask_filter_get_name, .create = mask_filter_create, .destroy = mask_filter_destroy, - .update = mask_filter_update, + .update = mask_filter_update_v2, .get_defaults = mask_filter_defaults, .get_properties = mask_filter_properties, .video_tick = mask_filter_tick, - .video_render = mask_filter_render, + .video_render = mask_filter_render_v2, }; diff --git a/plugins/obs-filters/obs-filters.c b/plugins/obs-filters/obs-filters.c index af82c0aaa..fe9b79b64 100644 --- a/plugins/obs-filters/obs-filters.c +++ b/plugins/obs-filters/obs-filters.c @@ -9,16 +9,21 @@ MODULE_EXPORT const char *obs_module_description(void) } extern struct obs_source_info mask_filter; +extern struct obs_source_info mask_filter_v2; extern struct obs_source_info crop_filter; extern struct obs_source_info gain_filter; extern struct obs_source_info color_filter; +extern struct obs_source_info color_filter_v2; extern struct obs_source_info scale_filter; extern struct obs_source_info scroll_filter; extern struct obs_source_info gpu_delay_filter; extern struct obs_source_info color_key_filter; +extern struct obs_source_info color_key_filter_v2; extern struct obs_source_info color_grade_filter; extern struct obs_source_info sharpness_filter; +extern struct obs_source_info sharpness_filter_v2; extern struct obs_source_info chroma_key_filter; +extern struct obs_source_info chroma_key_filter_v2; extern struct obs_source_info async_delay_filter; #if NOISEREDUCTION_ENABLED extern struct obs_source_info noise_suppress_filter; @@ -30,20 +35,26 @@ extern struct obs_source_info compressor_filter; extern struct obs_source_info limiter_filter; extern struct obs_source_info expander_filter; extern struct obs_source_info luma_key_filter; +extern struct obs_source_info luma_key_filter_v2; bool obs_module_load(void) { obs_register_source(&mask_filter); + obs_register_source(&mask_filter_v2); obs_register_source(&crop_filter); obs_register_source(&gain_filter); obs_register_source(&color_filter); + obs_register_source(&color_filter_v2); obs_register_source(&scale_filter); obs_register_source(&scroll_filter); obs_register_source(&gpu_delay_filter); obs_register_source(&color_key_filter); + obs_register_source(&color_key_filter_v2); obs_register_source(&color_grade_filter); obs_register_source(&sharpness_filter); + obs_register_source(&sharpness_filter_v2); obs_register_source(&chroma_key_filter); + obs_register_source(&chroma_key_filter_v2); obs_register_source(&async_delay_filter); #if NOISEREDUCTION_ENABLED obs_register_source(&noise_suppress_filter); @@ -55,5 +66,6 @@ bool obs_module_load(void) obs_register_source(&limiter_filter); obs_register_source(&expander_filter); obs_register_source(&luma_key_filter); + obs_register_source(&luma_key_filter_v2); return true; } diff --git a/plugins/obs-filters/scale-filter.c b/plugins/obs-filters/scale-filter.c index 79823f7b8..cb1c036d3 100644 --- a/plugins/obs-filters/scale-filter.c +++ b/plugins/obs-filters/scale-filter.c @@ -297,9 +297,11 @@ static void scale_filter_render(void *data, gs_effect_t *effect) gs_effect_set_next_sampler(filter->image_param, filter->point_sampler); + const bool previous = gs_set_linear_srgb(true); obs_source_process_filter_tech_end(filter->context, filter->effect, filter->cx_out, filter->cy_out, technique); + gs_set_linear_srgb(previous); UNUSED_PARAMETER(effect); } diff --git a/plugins/obs-filters/sharpness-filter.c b/plugins/obs-filters/sharpness-filter.c index b9328ab05..d59e1b22e 100644 --- a/plugins/obs-filters/sharpness-filter.c +++ b/plugins/obs-filters/sharpness-filter.c @@ -73,7 +73,7 @@ static void *sharpness_create(obs_data_t *settings, obs_source_t *context) return filter; } -static void sharpness_render(void *data, gs_effect_t *effect) +static void sharpness_render_internal(void *data, bool srgb) { struct sharpness_data *filter = data; @@ -90,7 +90,21 @@ static void sharpness_render(void *data, gs_effect_t *effect) gs_effect_set_float(filter->texture_width, filter->texwidth); gs_effect_set_float(filter->texture_height, filter->texheight); + const bool previous = gs_set_linear_srgb(srgb); obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + gs_set_linear_srgb(previous); +} + +static void sharpness_render_v1(void *data, gs_effect_t *effect) +{ + sharpness_render_internal(data, false); + + UNUSED_PARAMETER(effect); +} + +static void sharpness_render_v2(void *data, gs_effect_t *effect) +{ + sharpness_render_internal(data, true); UNUSED_PARAMETER(effect); } @@ -115,12 +129,26 @@ static void sharpness_defaults(obs_data_t *settings) struct obs_source_info sharpness_filter = { .id = "sharpness_filter", .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, + .get_name = sharpness_getname, + .create = sharpness_create, + .destroy = sharpness_destroy, + .update = sharpness_update, + .video_render = sharpness_render_v1, + .get_properties = sharpness_properties, + .get_defaults = sharpness_defaults, +}; + +struct obs_source_info sharpness_filter_v2 = { + .id = "sharpness_filter", + .version = 2, + .type = OBS_SOURCE_TYPE_FILTER, .output_flags = OBS_SOURCE_VIDEO, .get_name = sharpness_getname, .create = sharpness_create, .destroy = sharpness_destroy, .update = sharpness_update, - .video_render = sharpness_render, + .video_render = sharpness_render_v2, .get_properties = sharpness_properties, .get_defaults = sharpness_defaults, }; diff --git a/plugins/obs-text/gdiplus/obs-text.cpp b/plugins/obs-text/gdiplus/obs-text.cpp index d28e1f091..29f218146 100644 --- a/plugins/obs-text/gdiplus/obs-text.cpp +++ b/plugins/obs-text/gdiplus/obs-text.cpp @@ -871,15 +871,20 @@ inline void TextSource::Render() gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); gs_technique_t *tech = gs_effect_get_technique(effect, "Draw"); + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(true); + gs_technique_begin(tech); gs_technique_begin_pass(tech, 0); - gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), - tex); + gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"), + tex); gs_draw_sprite(tex, 0, cx, cy); gs_technique_end_pass(tech); gs_technique_end(tech); + + gs_enable_framebuffer_srgb(previous); } /* ------------------------------------------------------------------------- */ diff --git a/plugins/obs-transitions/data/fade_to_color_transition.effect b/plugins/obs-transitions/data/fade_to_color_transition.effect index 0e8417c7c..4f069358b 100644 --- a/plugins/obs-transitions/data/fade_to_color_transition.effect +++ b/plugins/obs-transitions/data/fade_to_color_transition.effect @@ -24,8 +24,7 @@ VertData VSDefault(VertData v_in) float4 PSFadeToColor(VertData v_in) : TARGET { - float4 premultiplied = float4(color.rgb * color.a, color.a); - return lerp(tex.Sample(textureSampler, v_in.uv), premultiplied, swp); + return lerp(tex.Sample(textureSampler, v_in.uv), color, swp); } technique FadeToColor diff --git a/plugins/obs-transitions/transition-fade-to-color.c b/plugins/obs-transitions/transition-fade-to-color.c index d5f647f76..fdac3c819 100644 --- a/plugins/obs-transitions/transition-fade-to-color.c +++ b/plugins/obs-transitions/transition-fade-to-color.c @@ -52,7 +52,7 @@ static void fade_to_color_update(void *data, obs_data_t *settings) color |= 0xFF000000; - vec4_from_rgba(&fade_to_color->color, color); + vec4_from_rgba_srgb_premultiply(&fade_to_color->color, color); fade_to_color->switch_point = (float)swp / 100.0f; } @@ -105,13 +105,19 @@ static void fade_to_color_callback(void *data, gs_texture_t *a, gs_texture_t *b, float swp = t < fade_to_color->switch_point ? sa : 1.0f - sb; - gs_effect_set_texture(fade_to_color->ep_tex, - t < fade_to_color->switch_point ? a : b); - gs_effect_set_float(fade_to_color->ep_swp, swp); + gs_texture_t *const tex = (t < fade_to_color->switch_point) ? a : b; + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(true); + + gs_effect_set_texture_srgb(fade_to_color->ep_tex, tex); gs_effect_set_vec4(fade_to_color->ep_color, &fade_to_color->color); + gs_effect_set_float(fade_to_color->ep_swp, swp); while (gs_effect_loop(fade_to_color->effect, "FadeToColor")) gs_draw_sprite(NULL, 0, cx, cy); + + gs_enable_framebuffer_srgb(previous); } static void fade_to_color_video_render(void *data, gs_effect_t *effect) diff --git a/plugins/obs-transitions/transition-fade.c b/plugins/obs-transitions/transition-fade.c index 9358de91a..d247ed6dd 100644 --- a/plugins/obs-transitions/transition-fade.c +++ b/plugins/obs-transitions/transition-fade.c @@ -53,12 +53,17 @@ static void fade_callback(void *data, gs_texture_t *a, gs_texture_t *b, float t, { struct fade_info *fade = data; - gs_effect_set_texture(fade->a_param, a); - gs_effect_set_texture(fade->b_param, b); + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(true); + + gs_effect_set_texture_srgb(fade->a_param, a); + gs_effect_set_texture_srgb(fade->b_param, b); gs_effect_set_float(fade->fade_param, t); while (gs_effect_loop(fade->effect, "Fade")) gs_draw_sprite(NULL, 0, cx, cy); + + gs_enable_framebuffer_srgb(previous); } static void fade_video_render(void *data, gs_effect_t *effect) diff --git a/plugins/obs-transitions/transition-luma-wipe.c b/plugins/obs-transitions/transition-luma-wipe.c index 9af247922..1a1016e08 100644 --- a/plugins/obs-transitions/transition-luma-wipe.c +++ b/plugins/obs-transitions/transition-luma-wipe.c @@ -165,8 +165,11 @@ static void luma_wipe_callback(void *data, gs_texture_t *a, gs_texture_t *b, { struct luma_wipe_info *lwipe = data; - gs_effect_set_texture(lwipe->ep_a_tex, a); - gs_effect_set_texture(lwipe->ep_b_tex, b); + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(true); + + gs_effect_set_texture_srgb(lwipe->ep_a_tex, a); + gs_effect_set_texture_srgb(lwipe->ep_b_tex, b); gs_effect_set_texture(lwipe->ep_l_tex, lwipe->luma_image.texture); gs_effect_set_float(lwipe->ep_progress, t); @@ -175,6 +178,8 @@ static void luma_wipe_callback(void *data, gs_texture_t *a, gs_texture_t *b, while (gs_effect_loop(lwipe->effect, "LumaWipe")) gs_draw_sprite(NULL, 0, cx, cy); + + gs_enable_framebuffer_srgb(previous); } void luma_wipe_video_render(void *data, gs_effect_t *effect) diff --git a/plugins/obs-transitions/transition-slide.c b/plugins/obs-transitions/transition-slide.c index b3c1e53a2..900303f7c 100644 --- a/plugins/obs-transitions/transition-slide.c +++ b/plugins/obs-transitions/transition-slide.c @@ -93,14 +93,26 @@ static void slide_callback(void *data, gs_texture_t *a, gs_texture_t *b, vec2_mulf(&tex_a_dir, &tex_a_dir, t); vec2_mulf(&tex_b_dir, &tex_b_dir, 1.0f - t); - gs_effect_set_texture(slide->a_param, a); - gs_effect_set_texture(slide->b_param, b); + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) { + gs_effect_set_texture_srgb(slide->a_param, a); + gs_effect_set_texture_srgb(slide->b_param, b); + } else { + gs_effect_set_texture(slide->a_param, a); + gs_effect_set_texture(slide->b_param, b); + } gs_effect_set_vec2(slide->tex_a_dir_param, &tex_a_dir); gs_effect_set_vec2(slide->tex_b_dir_param, &tex_b_dir); while (gs_effect_loop(slide->effect, "Slide")) gs_draw_sprite(NULL, 0, cx, cy); + + gs_enable_framebuffer_srgb(previous); } void slide_video_render(void *data, gs_effect_t *effect) diff --git a/plugins/obs-transitions/transition-swipe.c b/plugins/obs-transitions/transition-swipe.c index 24b87c9af..9a1cc1248 100644 --- a/plugins/obs-transitions/transition-swipe.c +++ b/plugins/obs-transitions/transition-swipe.c @@ -88,12 +88,26 @@ static void swipe_callback(void *data, gs_texture_t *a, gs_texture_t *b, vec2_mulf(&swipe_val, &swipe_val, swipe->swipe_in ? 1.0f - t : t); - gs_effect_set_texture(swipe->a_param, swipe->swipe_in ? b : a); - gs_effect_set_texture(swipe->b_param, swipe->swipe_in ? a : b); + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + gs_texture_t *t0 = swipe->swipe_in ? b : a; + gs_texture_t *t1 = swipe->swipe_in ? a : b; + if (linear_srgb) { + gs_effect_set_texture_srgb(swipe->a_param, t0); + gs_effect_set_texture_srgb(swipe->b_param, t1); + } else { + gs_effect_set_texture(swipe->a_param, t0); + gs_effect_set_texture(swipe->b_param, t1); + } gs_effect_set_vec2(swipe->swipe_param, &swipe_val); while (gs_effect_loop(swipe->effect, "Swipe")) gs_draw_sprite(NULL, 0, cx, cy); + + gs_enable_framebuffer_srgb(previous); } static void swipe_video_render(void *data, gs_effect_t *effect) diff --git a/plugins/text-freetype2/obs-convenience.c b/plugins/text-freetype2/obs-convenience.c index 2670e6907..3607a9a81 100644 --- a/plugins/text-freetype2/obs-convenience.c +++ b/plugins/text-freetype2/obs-convenience.c @@ -67,6 +67,11 @@ void draw_uv_vbuffer(gs_vertbuffer_t *vbuf, gs_texture_t *tex, if (vbuf == NULL || tex == NULL) return; + const bool linear_srgb = gs_get_linear_srgb(); + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + gs_vertexbuffer_flush(vbuf); gs_load_vertexbuffer(vbuf); gs_load_indexbuffer(NULL); @@ -75,7 +80,10 @@ void draw_uv_vbuffer(gs_vertbuffer_t *vbuf, gs_texture_t *tex, for (size_t i = 0; i < passes; i++) { if (gs_technique_begin_pass(tech, i)) { - gs_effect_set_texture(image, texture); + if (linear_srgb) + gs_effect_set_texture_srgb(image, texture); + else + gs_effect_set_texture(image, texture); gs_draw(GS_TRIS, 0, num_verts); @@ -84,4 +92,6 @@ void draw_uv_vbuffer(gs_vertbuffer_t *vbuf, gs_texture_t *tex, } gs_technique_end(tech); + + gs_enable_framebuffer_srgb(previous); } diff --git a/plugins/text-freetype2/text-freetype2.c b/plugins/text-freetype2/text-freetype2.c index fec89b94a..573d20291 100644 --- a/plugins/text-freetype2/text-freetype2.c +++ b/plugins/text-freetype2/text-freetype2.c @@ -247,6 +247,8 @@ static void ft2_source_render(void *data, gs_effect_t *effect) if (srcdata->text == NULL || *srcdata->text == 0) return; + const bool previous = gs_set_linear_srgb(true); + gs_reset_blend_state(); if (srcdata->outline_text) draw_outlines(srcdata); @@ -256,6 +258,8 @@ static void ft2_source_render(void *data, gs_effect_t *effect) draw_uv_vbuffer(srcdata->vbuf, srcdata->tex, srcdata->draw_effect, (uint32_t)wcslen(srcdata->text) * 6); + gs_set_linear_srgb(previous); + UNUSED_PARAMETER(effect); } diff --git a/plugins/win-capture/dc-capture.c b/plugins/win-capture/dc-capture.c index 6ebdca0b9..7cb317225 100644 --- a/plugins/win-capture/dc-capture.c +++ b/plugins/win-capture/dc-capture.c @@ -172,7 +172,15 @@ static void draw_texture(struct dc_capture *capture, gs_effect_t *effect) gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); size_t passes; - gs_effect_set_texture(image, texture); + const bool linear_srgb = gs_get_linear_srgb() && capture->compatibility; + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(linear_srgb); + + if (linear_srgb) + gs_effect_set_texture_srgb(image, texture); + else + gs_effect_set_texture(image, texture); passes = gs_technique_begin(tech); for (size_t i = 0; i < passes; i++) { @@ -186,6 +194,8 @@ static void draw_texture(struct dc_capture *capture, gs_effect_t *effect) } } gs_technique_end(tech); + + gs_enable_framebuffer_srgb(previous); } void dc_capture_render(struct dc_capture *capture, gs_effect_t *effect) diff --git a/plugins/win-capture/duplicator-monitor-capture.c b/plugins/win-capture/duplicator-monitor-capture.c index 6ec84bb19..d7def0ad8 100644 --- a/plugins/win-capture/duplicator-monitor-capture.c +++ b/plugins/win-capture/duplicator-monitor-capture.c @@ -236,6 +236,8 @@ static void duplicator_capture_render(void *data, gs_effect_t *effect) rot = capture->rot; + const bool previous = gs_set_linear_srgb(false); + while (gs_effect_loop(effect, "Draw")) { if (rot != 0) { float x = 0.0f; @@ -265,6 +267,8 @@ static void duplicator_capture_render(void *data, gs_effect_t *effect) gs_matrix_pop(); } + gs_set_linear_srgb(previous); + if (capture->capture_cursor) { effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); diff --git a/plugins/win-capture/game-capture.c b/plugins/win-capture/game-capture.c index 27a49fece..dce5d1beb 100644 --- a/plugins/win-capture/game-capture.c +++ b/plugins/win-capture/game-capture.c @@ -149,6 +149,7 @@ struct game_capture { ipc_pipe_server_t pipe; gs_texture_t *texture; + bool supports_srgb; struct hook_info *global_hook_info; HANDLE keepalive_mutex; HANDLE hook_init; @@ -1574,6 +1575,7 @@ static inline bool init_shmem_capture(struct game_capture *gc) return false; } + gc->supports_srgb = true; gc->copy_texture = copy_shmem_tex; return true; } @@ -1583,6 +1585,8 @@ static inline bool init_shtex_capture(struct game_capture *gc) obs_enter_graphics(); gs_texture_destroy(gc->texture); gc->texture = gs_texture_open_shared(gc->shtex_data->tex_handle); + enum gs_color_format format = gs_texture_get_color_format(gc->texture); + gc->supports_srgb = gs_is_srgb_format(format); obs_leave_graphics(); if (!gc->texture) { @@ -1808,6 +1812,9 @@ static void game_capture_render(void *data, gs_effect_t *effect) ? OBS_EFFECT_DEFAULT : OBS_EFFECT_OPAQUE); + const bool linear_srgb = gs_get_linear_srgb() && gc->supports_srgb; + const bool previous = gs_set_linear_srgb(linear_srgb); + while (gs_effect_loop(effect, "Draw")) { obs_source_draw(gc->texture, 0, 0, 0, 0, gc->global_hook_info->flip); @@ -1818,6 +1825,8 @@ static void game_capture_render(void *data, gs_effect_t *effect) } } + gs_set_linear_srgb(previous); + if (!gc->config.allow_transparency && gc->config.cursor && !gc->cursor_hidden) { effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);