From 66259560e03be5576bfad7f17aedc76a0b5a136d Mon Sep 17 00:00:00 2001 From: jpark37 Date: Tue, 19 Jan 2021 15:00:51 -0800 Subject: [PATCH] libobs: Add dormant SRGB format support GS_RGBA, GS_BGRX, and GS_BGRA now use TYPELESS DXGI formats, so we can alias them between UNORM and UNORM_SRGB as necessary. GS_RGBA_UNORM, GS_BGRX_UNORM, and GS_BGRA_UNORM have been added to support straight UNORM types, which Windows requires for sharing textures from D3D9 and OpenGL. The D3D path aliases via views, and GL aliases via GL_EXT_texture_sRGB_decode/GL_FRAMEBUFFER_SRGB. A significant amount of code has changed in the D3D/GL backends, but the concepts are simple. On the D3D side, we need separate SRVs and RTVs to support nonlinear/linear reads and writes. On the GL side, we need to set the proper GL parameters to emulate the same. Add gs_enable_framebuffer_srgb/gs_framebuffer_srgb_enabled to set/get the framebuffer as SRGB or not. Add gs_linear_srgb_active/gs_set_linear_srgb to instruct sources that they should render as SRGB. Legacy sources can ignore this setting without regression. Update obs_source_draw to use linear SRGB as needed. Update render_filter_tex to use linear SRGB as needed. Add gs_effect_set_texture_srgb next to gs_effect_set_texture to set texture with SRGB view instead. Add SRGB helpers for vec4 struct. Create GDI-compatible textures without SRGB support. Doesn't seem to work with SRGB formats. --- libobs-d3d11/d3d11-rebuild.cpp | 104 +++++++++++++++---- libobs-d3d11/d3d11-shader.cpp | 13 ++- libobs-d3d11/d3d11-stagesurf.cpp | 2 +- libobs-d3d11/d3d11-subsystem.cpp | 151 ++++++++++++++++++---------- libobs-d3d11/d3d11-subsystem.hpp | 74 ++++++++++++-- libobs-d3d11/d3d11-texture2d.cpp | 111 ++++++++++++-------- libobs-d3d11/d3d11-texture3d.cpp | 51 ++++++---- libobs-opengl/gl-shader.c | 21 ++-- libobs-opengl/gl-subsystem.c | 38 ++++++- libobs-opengl/gl-subsystem.h | 25 ++++- libobs/graphics/device-exports.h | 4 + libobs/graphics/effect.c | 13 ++- libobs/graphics/graphics-imports.c | 2 + libobs/graphics/graphics-internal.h | 5 + libobs/graphics/graphics.c | 44 ++++++++ libobs/graphics/graphics.h | 33 ++++++ libobs/graphics/vec4.h | 115 ++++++++++++++++++--- libobs/obs-source.c | 48 +++++++-- 18 files changed, 667 insertions(+), 187 deletions(-) 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-subsystem.c b/libobs-opengl/gl-subsystem.c index 073ac40c8..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) @@ -492,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; @@ -530,6 +537,10 @@ void device_load_texture(gs_device_t *device, gs_texture_t *tex, int unit) 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; @@ -539,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) { @@ -863,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 fd623c5dd..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; 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-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)