1
0
Эх сурвалжийг харах

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.
jpark37 4 жил өмнө
parent
commit
66259560e0

+ 83 - 21
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;
 

+ 9 - 4
libobs-d3d11/d3d11-shader.cpp

@@ -262,10 +262,15 @@ inline void gs_shader::UpdateParam(vector<uint8_t> &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;

+ 1 - 1
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;
 

+ 100 - 51
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<gs_texture_2d *>(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<gs_texture_2d *>(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<gs_texture_2d *>(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<gs_texture_2d *>(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);

+ 63 - 11
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<ID3D11ShaderResourceView> shaderRes;
-	D3D11_SHADER_RESOURCE_VIEW_DESC resourceDesc = {};
+	ComPtr<ID3D11ShaderResourceView> 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<ID3D11Texture2D> texture;
 	ComPtr<ID3D11RenderTargetView> renderTarget[6];
+	ComPtr<ID3D11RenderTargetView> renderTargetLinear[6];
 	ComPtr<IDXGISurface1> 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<ID3D11RenderTargetView> &rt : renderTarget)
+			rt.Release();
+		for (ComPtr<ID3D11RenderTargetView> &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();

+ 69 - 42
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;
-
-	memset(&resourceDesc, 0, sizeof(resourceDesc));
-	resourceDesc.Format = ConvertGSTextureFormat(this->format);
-	resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
-	resourceDesc.Texture2D.MipLevels = 1;
+	this->dxgiFormatResource = ConvertGSTextureFormatResource(format);
+	this->dxgiFormatView = ConvertGSTextureFormatView(format);
+	this->dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format);
 
-	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;
-
-	memset(&resourceDesc, 0, sizeof(resourceDesc));
-	resourceDesc.Format = ConvertGSTextureFormat(this->format);
-	resourceDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
-	resourceDesc.Texture2D.MipLevels = 1;
+	this->dxgiFormatResource = ConvertGSTextureFormatResource(format);
+	this->dxgiFormatView = ConvertGSTextureFormatView(format);
+	this->dxgiFormatViewLinear = ConvertGSTextureFormatViewLinear(format);
 
-	HRESULT hr = device->device->CreateShaderResourceView(
-		texture, &resourceDesc, shaderRes.Assign());
-	if (FAILED(hr))
-		throw HRError("Failed to create shader resource view", hr);
+	InitResourceView();
 }

+ 30 - 21
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();
 }

+ 15 - 6
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)

+ 37 - 1
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,

+ 22 - 3
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;

+ 4 - 0
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,

+ 12 - 1
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)

+ 2 - 0
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);

+ 5 - 0
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;
 };

+ 44 - 0
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;

+ 33 - 0
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)
 {

+ 99 - 16
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,

+ 39 - 9
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)